JavaScript EventLoop
EventLoop 即事件循环机制,是指浏览器或 Node 的一种解决 JavaScript 单线程运行时不会阻塞的一种机制,也就是咱们常常应用异步的原理。
JavaScript 运行机制
- 所有同步工作都在主线程上执行,造成一个
执行栈(Execution Context Stack)
。 - 主线程之外,还存在
工作队列(Task Queue)
。只有异步工作有了运行后果,就在工作队列之中搁置一个事件。 - 一旦
执行栈
中的所有同步工作执行结束,零碎就会读取工作队列
,看看外面有哪些事件。如果有那些对应的异步工作
,于是完结期待状态,进入执行栈,开始执行。 - 主线程一直反复下面的第三步
- 一个事件循环中有一个或者是多个工作队列
总结:调用栈中的同步工作都执行结束,栈内被清空了,就代表主线程闲暇了。
这个时候就会去工作队列中依照程序读取一个工作放入到栈中执行。
每次栈内被清空,都会去读取工作队列有没有工作,有就读取执行,始终循环读取~执行的操作。
EventLoop 事件循环
介绍
主线程从“工作队列”中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:
- JavaScript 中有两种异步工作:
宏工作(MacroTask)
、微工作(MicroTask)
。 - 主线程会一直从工作队列中按程序取工作执行,每执行完一个工作都会查看『微工作』队列是否为空(执行完一个工作的具体标记是函数执行栈为空),如果不为空则会一次性执行完所有『微工作』。
- 而后再进入下一个循环去工作队列中取下一个工作执行。
MacroTask 宏工作
- script 全副代码、
setTimeout
、setInterval
、setImmediate
(浏览器临时不反对,只有 IE10 反对,具体可见 MDN。)、I/O
、UI Rendering
。
MicroTask 微工作
Process.nextTick
(Node独有)、Promise
、Object.observe(废除)
、MutationObserver
(具体应用形式查看这里)
同步工作和异步工作
JavaScript 单线程工作被分为同步工作和异步工作,同步工作会在调用栈中依照程序期待主线程顺次执行。异步工作会在异步工作有了后果后,将注册的回调函数放入工作队列中,期待主线程闲暇的时候(调用栈被清空),被读取到栈内期待主线程的执行。
事件循环的过程模型
- 抉择以后要执行的工作队列,抉择工作队列中最先进入的工作,如果工作队列为空即 null,则执行跳转到微工作的执行步骤。
- 将事件循环中的工作设置为以后抉择工作
- 执行工作
- 将事件循环中以后运行工作设置为 null
- 将曾经运行实现的工作从工作队列中删除
Microtasks 查看步骤,进入微工作检查点。
- 设置微工作检查点标记为 true。
- 当事件循环微工作执行队列不为空时:抉择一个
最先进入的微工作队列
的微工作,将事件循环的微工作设置为以后抉择的微工作
。 - 运行微工作
- 将曾经执行实现的微工作设置为 null
- 移除微工作队列中的以后运行实现的微工作
- 清理 IndexDB 事务
- 设置进入微工作检查点的标记为 false。
- 更新界面渲染
- 返回第一步
总结
- 执行栈在执行完 同步工作 后,查看 执行栈 是否为空,如果为空,就会去查看 微工作队列 是否为空,如果为空的话,就执行 宏工作,否则就一次性执行完 所有微工作。
- 每次单个 宏工作 执行结束后,查看 微工作队列 是否为空,如果不为空的话,会依照
先入先出
的规定全副执行完 微工作 后,设置 微工作队列 为 null,而后再执行宏工作,如此循环。
举个栗子
代码
console.log('script start');setTimeout(function () { console.log('setTimeout');}, 0);Promise.resolve() .then(function () { console.log('promise1'); }) .then(function () { console.log('promise2'); });console.log('script end');
执行后果
script startscript endpromise1promise2setTimeout
执行步骤
- 第一次执行:执行同步代码,将宏工作和微工作划分到各自队列中。
- 第二次执行:执行宏工作后,检测到 微工作队列 中不为空,执行
Promise1
,执行实现Promise1
后,调用Promise2.then
,放入 微工作队列 中,再执行Promise2.then
。 - 第三次执行:当 微工作队列 中为空时,执行 宏工作,执行
setTimeout callback
,打印日志。 - 第四次执行:清空工作队列和调用栈
图例
setTimeout(function () { console.log('timer1'); Promise.resolve() .then(function () { console.log('promise1'); });});setTimeout(function () { console.log('timer2'); Promise.resolve() .then(function () { console.log('promise2'); });});
再举个栗子
// 例子console.log('script start');async function async1() { await async2(); console.log('async1 end');}async function async2() { console.log('async2 end');}async1();setTimeout(function () { console.log('setTimeout');}, 0);new Promise((resolve) => { console.log('Promise'); resolve();}) .then(function () { console.log('promise1'); }) .then(function () { console.log('promise2'); });console.log('script end');// 后果script startasync2 endPromisescript endasync1 endpromise1promise2setTimeout// 只需将 async1 的执行了解为function async1() { return async2().then(() => { console.log('async1 end'); });}
{% video ./eventloop_demo.mp4 %}
执行步骤如上所示
开始执行
- 首先咱们执行同步代码,先打印
script start
。 - 再打印
async2 end
,将 async2.then 放入微工作队列中。 - 继续执行,将 setTimeout 放入宏工作队列中。
- 再打印
Promise
,将 Promise.then 放入微工作队列中。 - 最初打印
script end
- 首先咱们执行同步代码,先打印
执行实现后,查看微工作队列不为空,依照先进先出准则继续执行。
- 执行 async2.then 打印
async1 end
- 执行 Promise.then 打印
promise1
,并将 promise1.then 放入微工作队列中。
- 执行 async2.then 打印
此时查看微工作队列持续不为空
- 执行 promise1.then 打印
promise2
- 执行 promise1.then 打印
最初执行宏工作队列中的工作
- 执行 setTimeout,延迟时间到后,将其回调函数放入工作队列中。
执行回调函数
- 打印
setTimeout
- 打印
习题
题目
console.log('script start');async function async1() { await async2(); console.log('async1 end');}async function async2() { console.log('async2 end');}async1();setTimeout(function () { console.log('setTimeout1');}, 100);setTimeout(function () { console.log('setTimeout2');}, 10);setTimeout(function () { console.log('setTimeout3');}, 0);setTimeout(function () { console.log('setTimeout4');}, 0);new Promise((resolve) => { console.log('Promise'); setTimeout(function () { console.log('Promise-setTimeout'); }, 10); resolve();}) .then(function () { setTimeout(function () { console.log('promise1-setTimeout'); }, 10); console.log('promise1'); }) .then(function () { setTimeout(function () { console.log('promise2-setTimeout'); }, 0); console.log('promise2'); });console.log('script end');
答案
<details>
<summary>先思考再查看答案哦~</summary>
运行后果
script startasync2 endPromisescript endasync1 endpromise1promise2setTimeout3setTimeout4promise2-setTimeoutsetTimeout2Promise-setTimeoutpromise1-setTimeoutsetTimeout1
运行步骤
开始执行
- 首先咱们执行同步代码,先打印
script start
。 - 再打印
async2 end
,将 async2.then 放入微工作队列中。 - 继续执行,将 setTimeout1、setTimeout2、setTimeout3、setTimeout4 顺次放入宏工作队列中。
- 再打印
Promise
,将 Promise.then 放入微工作队列中,将 Promise-setTimeout 放入宏工作队列中。 - 最初打印
script end
- 首先咱们执行同步代码,先打印
执行实现后,查看微工作队列不为空,依照先进先出准则继续执行。
- 执行 async2.then 打印
async1 end
- 执行 Promise.then 将 promise1-setTimeout 放入宏工作队列中,打印
promise1
,并将 promise1.then 放入微工作队列中。
- 执行 async2.then 打印
此时查看微工作队列持续不为空
- 执行 promise1.then 将 promise2-setTimeout 放入宏工作队列中,打印
promise2
。
- 执行 promise1.then 将 promise2-setTimeout 放入宏工作队列中,打印
最初执行宏工作队列中的工作
- 顺次执行【setTimeout1、setTimeout2、setTimeout3、setTimeout4、Promise-setTimeout、promise1-setTimeout、promise2-setTimeout】。
- 期待延迟时间到后,将其回调函数放入工作队列中。
顺次执行回调函数
- 打印
setTimeout3
- 打印
setTimeout4
- 打印
promise2-setTimeout
- 打印
setTimeout2
- 打印
Promise-setTimeout
- 打印
promise1-setTimeout
- 打印
setTimeout1
- 打印
</details>
参考起源
- 并发模型与事件循环
- EventLoop
- Node EventLoop
- JavaScript 垃圾回收
- 阮一峰 EventLoop