李维亮_前端开发攻城狮 - 事件循环
type: channel
link: https://www.liweiliang.com/tag/%E4%BA%8B%E4%BB%B6%E5%BE%AA%E7%8E%AF/
items: 事件循环是如何影响页面渲染的？

事件循环是如何影响页面渲染的？
type: item
date: 2019-03-05T10:05:00+08:00
description: JavaScript 是单线程的，但提供了很多异步调用方式比如 setTimeout，setInterval，setImmediate，Promise.prototype.then，postMessage，requestAnimationFrame， I/O，DOM 事件等。 这些异步调用的实现都是事件循环，但根据插入的队列不同和取任务的时机不同他们的表现也不同。 尤其在涉及与页面渲染的关系时。TL;DR页面渲染/交互任务也会插入在 Task Queue 中，会与各种异步机制插入的任务交错执行。Microtask Queue 会在下一个任务开始之前清空。单个耗时任务和 Microtask Queue 都会阻塞页面交互，Task Queue 则不影响。渲染时机可以通过 requestAnimationFrame 精确控制。setImmediate 与 setTimeout 一样使用 Task Queue，但克服了 4ms 限制。任务与队列的概念JavaScript 的异步机制由 事件循环 实现，这些 API 的不同表现在进入和离开任务队列的时机。 为了讨论方便，先解释几个概念。任务与调用栈。由于单线程的特性，每个 JavaScript 执行上下文只有一个调用栈，其中保存着当前任务中所有未执行完的函数。只要调用栈非空，JavaScript 引擎就会持续地、不被打断地（从进程内的角度来看）执行完当前栈中的所有函数，因此 JavaScript 有 “run-to-completion” 的特性。调用栈被清空时意味着当前任务执行结束。Task Queue 是事件循环的主要数据结构。当前调用栈为空时（上一个任务已经完成），事件循环机制会持续地轮询 Task Queue，只要队列中有任务就拿出来执行。在任务执行期间插入的任务会进入 Task Queue 尾部。会加入 Task队列的包括：setTimeout, setInterval, setImmediate，postMessage，MessageChannel，UI 事件，I/O，页面渲染。Microtask Queue 在 Task Queue 的每个任务执行结束后，下一个任务执行开始前，会执行并清空 Microtask Queue 中的所有任务。在 Microtask 执行期间插入的任务也会进入当前 Microtask Queue。会加入MicroTask 队列的包括：Promise, MutationObserver，process.nextTick。上述异步 API 的分类依据的是最新标准或最新实现。存在一些例外，比如：Node < 9 的 process.nextTick 实现的是Task 语义（而非 Microtask）；IE8 中的 postMessage 是同步的；Edge 浏览器在点击事件处理函数之间不会清空Microtask Queue。无论是 Task Queue 还是 Microtask Queue，其中的 task 和 microtask 的执行都是异步的。 为了对上述两个队列有更直观的认识，这里举个例子：setTimeout(() => console.log('setTimeout')); Promise.resolve().then(() => { console.log('Promise'); Promise.resolve().then(() => console.log('Promise queued by Promise')); }); console.log('stack'); 上述代码片段中有两个Task（stack, setTimeout），两个 Microtask（Promise、Promise queued by Promise）。 stack 是当前任务会先执行；setTimeout 是第二个任务，在它执行前会清空 Microtask Queue。 这时 Microtask Queue 只有一个 Microtask（Promise）， 在它执行的过程中会插入第二个 Microtask（Promise queued by Promise）。 这些 Microtask 都会在下一个 Task（setTimeout）之前执行。因此输出为：stack Promise Promise queued by Promise setTimeout 注意与 .then 的回调不同，new Promise 的回调是同步执行的。可参考 Promise 回调的执行 一文。在 Jake 的 Tasks, microtasks, queues and schedules一文中有更加详细的例子，感兴趣的读者可前往观摩。何时会阻塞 UIUI 渲染和交互的处理是通过 T…