浏览器中 js 事件循环
一图胜千言
JS 引擎常驻于内存中,等待宿主将 JS 代码或函数传递给它。 也就是等待宿主环境分配宏观任务,反复等待 - 执行即为事件循环。
Event Loop 中,每一次循环称为 tick,每一次 tick 的任务如下:
- 执行栈选择最先进入队列的宏任务(一般都是 script),执行其同步代码直至结束;
- 检查是否存在微任务,有则会执行至微任务队列为空;
- 如果宿主为浏览器,可能会渲染页面;
- 开始下一轮 tick,执行宏任务中的异步代码(setTimeout 等回调)。
宏任务、微任务循环
ES6 规范中,microtask 称为 jobs,macrotask 称为 task 宏任务是由宿主发起的,而微任务由 JavaScript 自身发起。
宏任务(macrotask) | 微任务(microtask) | |
谁发起的 | 宿主(Node、浏览器) | JS 引擎 |
具体事件 | 1. script (可以理解为外层同步代码) 2. setTimeout/setInterval 3. UI rendering/UI 事件 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js) | 1. Promise 2. MutaionObserver 3. Object.observe(已废弃;Proxy 对象替代) 4. process.nextTick(Node.js) |
谁先运行 | 后运行 | 先运行 |
会触发新一轮 Tick 吗 | 会 | 不会 |
分析代码
1console.log("1");23setTimeout(function () {4 console.log("2");5 process.nextTick(function () {6 console.log("3");7 });8 new Promise(function (resolve) {9 console.log("4");10 resolve();11 }).then(function () {12 console.log("5");13 });14});15process.nextTick(function () {16 console.log("6");17});18new Promise(function (resolve) {19 console.log("7");20 resolve();21}).then(function () {22 console.log("8");23});2425setTimeout(function () {26 console.log("9");27 process.nextTick(function () {28 console.log("10");29 });30 new Promise(function (resolve) {31 console.log("11");32 resolve();33 }).then(function () {34 console.log("12");35 });36});
整段代码,共进行了三次事件循环,完整的输出为 1,7,6,8,2,4,3,5,9,11,10,12。 (请注意,node 环境下的事件监听依赖 libuv 与前端环境不完全相同,输出顺序可能会有误差)