事件循环的基本概念

  • JS执行的过程中,由JS引擎控制的函数调用栈来控制时间循环
  • 定时器线程,事件触发线程,异步http请求线程控制异步的任务队列
  • 任务分为macro task,micro task 对应都有不同的任务队列
    • macro task:script正常代码,setTimeout,setInterval,I/O,UI rendering

      •   由事件触发线程维护  
    • micro task:process.nextTick,promise,mutationObserve 
      •   由JS引擎线程维护
    • 最终在函数调用栈中完成

事件循环执行的顺序

  • 执行函数调用栈中的macro task,直到调用栈清空(剩下全局)
  • 执行job queues中所有可执行的micro tasks
  • 执行UI render
  • 从事件队列中获取macro task,开始新的事件循环

例子

<div class="outer" style="width:200px;height:200px;background-color: #ccc">
1
<div class="inner" style="width:100px;height:100px;background-color: #ddd">begin</div>
</div>
// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner'); var i = 0; // Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
console.log('mutate');
}).observe(outer, {
attributes: true
}); // Here's a click listener…
function onClick() {
i++; if(i === 1) {
inner.innerHTML = 'end';
} console.log('click'); setTimeout(function() {
alert('锚点');
console.log('timeout');
}, 0); Promise.resolve().then(function() {
console.log('promise');
}); outer.setAttribute('data-random', Math.random());
} // …which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);

当我们点击 inner div 时程序依次的执行顺序是:

  1. onclick 入 JS stack
  2. 打印出 click
  3. 将 timeout 压入到 macrotask
  4. 将 promise 压入到 microtask
  5. 修改 outer 属性 data-random
  6. 将 mutate 压入到 microtask,
  7. onclick 出 JS stack

此时,由于用户点击事件onclick产生的macrotask执行完毕,JS stack 清空,开始执行microtask.

  1. promise 入 JS stack
  2. 打印出 promise
  3. promise 出 JS stack
  4. mutate 入 JS stack
  5. 打印出 mutate
  6. mutate 出 JS stack

此时,microtask 执行完毕,JS stack 清空,但是由于事件冒泡,接着执行outer上的onclick事件.

  1. onclick 入 JS stack
  2. 打印出 click
  3. 将 timeout 压入到 macrotask
  4. 将 promise 压入到 microtask
  5. 修改 outer 属性 data-random
  6. 将 mutate 压入到 microtask,
  7. onclick 出 JS stack

此时,由于outer上的onclick事件产生的macrotask执行完毕,JS stack 清空,开始执行microtask.

  1. promise 入 JS stack
  2. 打印出 promise
  3. promise 出 JS stack
  4. mutate 入 JS stack
  5. 打印出 mutate
  6. mutate 出 JS stack

此时,本轮事件循环结束,UI 开始 render.

  1. 页面中inner的innerHTML变为end

此时,UI render 完毕,开始下一轮事件循环.

  1. timeout 入 JS stack
  2. 弹出警告 锚点.
  3. 打印出 timeout
  4. timeout 出 JS stack
  5. timeout 入 JS stack
  6. 弹出警告 锚点.
  7. 打印出 timeout
  8. timeout 出 JS stack

到此为止,整个事件执行完毕,我们可以看到在弹出警告框之前inner的内容已经改变

那如果不是用户点击事件触发onclick,而是js触发呢?
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
inner.click();

此时的执行顺序是:

  1. 首先是script(整体代码)入 JS stack
  2. onclick 入 JS stack
  3. 打印出 click
  4. 将 timeout 压入到 macrotask
  5. 将 promise 压入到 microtask
  6. 修改 outer 属性 data-random
  7. 将 mutate 压入到 microtask,
  8. onclick 出 JS stack

此时,inner 的 onclick 已经出 JS stack,但是script(整体代码)还没有出 JS stack,还不能执行microtask,由于冒泡,接着执行 outer 的 onclick.

  1. onclick 入 JS stack
  2. 打印出 click
  3. 将 timeout 压入到 macrotask
  4. 将 promise 压入到 microtask
  5. 修改 outer 属性 data-random

接着执行的outer.setAttribute('data-random', Math.random());,但是由于上一个mutation microtask还处于等待状态,不能再添加mutation microtask,所以这里不会将 mutate 压入到 microtask。接着执行:

  1. onclick 出 JS stack
  2. script(整体代码)出 JS stack

此时,inner.click()执行完毕,script(整体代码)已出 JS stack,JS stack 清空,开始执行mircotask.

  1. promise 入 JS stack
  2. 打印出 promise
  3. promise 出 JS stack
  4. mutate 入 JS stack
  5. 打印出 mutate
  6. mutate 出 JS stack
  7. promise 入 JS stack
  8. 打印出 promise
  9. promise 出 JS stack

此时,所有的mircotask执行完毕,本轮事件循环结束,UI 开始 render.

  1. 页面中inner的innerHTML变为end

此时,UI render 完毕,开始下一轮事件循环.

  1. timeout 入 JS stack
  2. 弹出警告 锚点.
  3. 打印出 timeout
  4. timeout 出 JS stack
  5. timeout 入 JS stack
  6. 弹出警告 锚点.
  7. 打印出 timeout
  8. timeout 出 JS stack

到此为止,整个事件执行完毕,我们可以看到在弹出警告框之前inner的内容已经改变

参考文献:

https://segmentfault.com/a/1190000013212944

http://zhangxiang958.github.io/2018/02/03/Event%20Loop%20中的%20microtask%20与%20macrotask/

JS事件循环,MACRO TASK,MICRO TASK的更多相关文章

  1. Node.js 事件循环(Event Loop)介绍

    Node.js 事件循环(Event Loop)介绍 JavaScript是一种单线程运行但又绝不会阻塞的语言,其实现非阻塞的关键是“事件循环”和“回调机制”.Node.js在JavaScript的基 ...

  2. Node.js事件循环

    Node JS是单线程应用程序,但它通过事件和回调概念,支持并发. 由于Node JS每一个API是异步的,作为一个单独的线程,它使用异步函数调用,以保持并发性.Node JS使用观察者模式.Node ...

  3. js事件循环机制辨析

     对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了.最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他.接下来我想要和大家分享一下,虽然可能有些许错误的 ...

  4. 6、Node.js 事件循环

    #########################################################################################Node.js 事件循 ...

  5. js——事件循环

    JS-事件循环 js运行的环境称之为宿主环境. 执行栈 :call stack ,一个数据结构,用于存放各种函数的执行环境,每一个函数执行之前他的相关信息会加入到执行栈中,函数调用之前,创建执行环境, ...

  6. 深入理解 JavaScript 事件循环(二)— task and microtask

    引言 microtask 这一名词是 JS 中比较新的概念,几乎所有人都是在学习 ES6 的 Promise 时才接触这一新概念,我也不例外.当我刚开始学习 Promise 的时候,对其中回调函数的执 ...

  7. [译] 深入理解 JavaScript 事件循环(二)— task and microtask

    引言 microtask 这一名词是 JS 中比较新的概念,几乎所有人都是在学习 ES6 的 Promise 时才接触这一新概念,我也不例外.当我刚开始学习 Promise 的时候,对其中回调函数的执 ...

  8. JS事件循环(Event Loop)机制

    前言 众所周知,为了与浏览器进行交互,Javascript是一门非阻塞单线程脚本语言. 为何单线程? 因为如果在DOM操作中,有两个线程一个添加节点,一个删除节点,浏览器并不知道以哪个为准,所以只能选 ...

  9. js事件循环

    之前有看过一些事件循环的博客,不过一阵子没看就发现自己忘光了,所以决定来自己写一个博客总结下! 首先,我们来解释下事件循环是个什么东西: 就我们所知,浏览器的js是单线程的,也就是说,在同一时刻,最多 ...

随机推荐

  1. Docker实战部署应用——Redis

    Redis 部署 拉取Redis镜像 docker pull redis 创建Redis容器 docker run -id --name=sun_redis -p 6379:6379 redis 客户 ...

  2. Bootstrap框架如何设置导入链接

    bootstrap文件划分: 导入规则: 方式一:可以先下载 ----https://v3.bootcss.com/ (1) 注意事项: (2) (3) 注意1:bootstrap中的js文件依赖于j ...

  3. openstack stein部署手册 3. keystone

    # 建立数据库用户及权限 create database keystone; grant all privileges on keystone.* to keystone@'localhost' id ...

  4. python函数传参和返回值注意事项

    函数传参 空参数 定义函数时括号里面没有形参,调用时不用传参. def func(): print('null para.') # 调用 func() 位置传参 规定形参的数量,调用时必须传递相同数量 ...

  5. javaweb各种框架组合案例(九):springboot+tk.mybatis+通用service

    一.项目结构 二.pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns= ...

  6. ghci对haskell的类型推导

    今天这篇文章分析一下ghci交互解释器对类型的推导. 假设有函数fn定义如下: let fn = map map 现在fn的类型是: map map :: [a -> b] -> [[a] ...

  7. 【学习】005 线程池原理分析&锁的深度化

    线程池 什么是线程池 Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序 都可以使用线程池.在开发过程中,合理地使用线程池能够带来3个好处. 第一:降低资源消耗.通过重复 ...

  8. $LCT$维护子树信息学习笔记

    \(LCT\)维护子树信息学习笔记 昨天\(FDF\)好题分享投了 \([ZJOI2018]\)历史 这题. 然后我顺势学学这个姿势. 结果调了一年...于是写个笔记记录一下. 基本原理 比较显然地, ...

  9. 关于python读写文件的r+方式的坑

    写脚本的时候需要将文件中的一行修改,我的修改逻辑是,用r+方式打开文件,然后将原文件数据读入一个数组,修改数组的对应元素,在seek(0),然后将数组write进文件 结果: 文件文件末尾总是多出一行 ...

  10. Task7.卷积神经网络

    卷积定义: 所谓卷积,其实是一种数学运算.但是在我们的学习生涯中,往往它都是披上了一层外衣,使得我们经常知其然不知其所以然.比如在信号系统中,他是以一维卷积的形式出现描述系统脉冲响应.又比如在图像处理 ...