事件循环

事件循环(Event-loop)

定义

javascript 是一门单线程语言,但是要知道浏览器本身不是单线程的(还有其他的工作线程:处理 AJAX 请求的线程、处理 DOM 事件的线程、定时器线程、读写文件的线程(例如在 Node.js 中)等等),是指 js 引擎是单线程的,即在 JS 引擎中负责解释和执行的线程只有一个,就是所谓的主线程(js 引擎,e.g. v8 引擎)。

同步任务和异步任务

js 的任务分为同步和异步两种,处理方式不同。

  • 同步任务

    • 在主线程上同步执行

  • 异步任务

    • 进入事件队列,把事件注入到调用栈内等待主线程调用执行

宏任务和微任务

进一步细分,JS 又可以分成宏任务(macrotask)和微任务(microtask)。宏任务事件是放在事件队列(macrotask 队列)中的,;微任务是放在微任务队列(microtask 队列)中。在 ECMAScript 中,microtask 称为 jobs,macrotask 可称为 task。

  • macrotask:

    • script(整体代码)、setTimeout、setInterval、requestAnimationFrame、I/O、事件、MessageChannel、setImmediate (Node.js)

  • microtask:

    • Promise.then、 MutaionObserver、process.nextTick (Node.js)

  • (补充:在 node 环境下,process.nextTick 的优先级高于 Promise)

事件循环的流程

  1. 检查 macrotask 队列是否为空,非空则到 2,为空则到 3

  2. 执行 macrotask 中的一个任务

  3. 检查 microtask 队列是否为空,若有则到 4,否则到 5

  4. 取出 microtask 中的任务执行,执行完成返回到步骤 3

  5. 执行视图更新

image

example

console.log('start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve()
  .then(function() {
    console.log('promise1');
  })
  .then(function() {
    console.log('promise2');
  });

console.log('end');

执行顺序:

  1. 首先全局代码(Main())被压入执行栈中,执行宏任务,打印start

  2. 遇到 setTimeout 放入宏任务队列(被推入事件队列的时机是计时器线程完成计时);

  3. 遇到 Promise,把第一个 then 的 callback 放入微任务队列;

  4. 继续执行宏任务,打印end

  5. 宏任务结束,调用栈当前为空,首先查找微任务队列;

  6. 执行微任务队列中的任务,打印promise1

  7. 执行完第一个 then 后返回后(返回 undefined),promise 状态变成 fullfill,出发第二个 then 执行,同理推入微任务队列;

  8. 继续查找微任务队列,打印promise2

  9. 此时微任务队列为空,此轮事件循环结束(如果有 ui 渲染会在这里执行);

  10. 开始执行下一轮事件循环,首先检查宏任务队列,发现 setTimeout,执行打印setTimeout

image

执行结果: start end promise1 promise2 setTimeout

相关

setTimeout & setInterval

  • W3C 在 HTML 标准中规定,规定要求 setTimeout 中低于 4ms 的时间间隔算为 4ms。

    用 setTimeout 模拟定期计时和直接用 setInterval 是有区别的。 因为每次 setTimeout 计时到后就会去执行,然后执行一段时间后才会继续 setTimeout,中间就多了误差(误差多少与代码执行时间有关) 而 setInterval 则是每次都精确的隔一段时间推入一个事件(但是,事件的实际执行时间不一定就准确,还有可能是这个事件还没执行完毕,下一个事件就来了)

    而且 setInterval 有一些比较致命的问题就是:

    累计效应(上面提到的),如果 setInterval 代码在(setInterval)再次添加到队列之前还没有完成执行,就会导致定时器代码连续运行好几次,而之间没有间隔。就算正常间隔执行,多个 setInterval 的代码执行时间可能会比预期小(因为代码执行需要一定时间)

    而且把浏览器最小化显示等操作时,setInterval 并不是不执行程序, 它会把 setInterval 的回调函数放在队列中,等浏览器窗口再次打开时,一瞬间全部执行

requestAnimationFrame

浏览器有自己的优化策略,例如把几次的视图更新累积到一起重绘,重绘之前会通知 requestAnimationFrame 执行回调函数,也就是说 requestAnimationFrame 回调的执行时机是在一次或多次事件循环的 UI render 阶段。浏览器只保证 requestAnimationFrame 的回调在重绘之前执行,没有确定的时间,何时重绘由浏览器决定.

rAF执行机制详解

Last updated

Was this helpful?