不管是浏览器的Event loop 仍是 Nodejs 中的Event loop,都是基于JS 的单线程设计而生的(纯属我的理解),经过事件循环实现非阻塞异步执行效果。node
谈到事件机制首先弄清楚主线程
,执行栈
, 任务队列
这几个概念。web
主线程:运行JS 代码。JS 的单线程针对的是单一的主线程,不是只能有一个线程。编程
执行栈:关于执行栈的概念结合执行上下文来看,《JavaScript深刻之执行上下文栈》promise
任务队列Task Queue:即队列,是一种先进先出的数据结构浏览器
三者的关系是:主线程要执行的都在执行栈里,执行栈里的内容是从任务队列里拿过来的。bash
任务能够划分为宏任务
和微任务
。数据结构
宏任务MacroTask
: 整个script , setTimeout, setInterval, setImmidiate(浏览器暂时不支持,只有IE10,可是在nodejs 中经常使用到)、I/O、UI Redering。异步
微任务(MicroTask)
:Promise, Async/Await(底层就是Promise)、Process.nextTick (node独有)、MutationObserver。async
具体执行时,任务还有另外一种的划分就是同步任务
和异步任务
函数
对于同步任务来讲 ,放入执行栈里按序依次执行的,可是对于异步任务而言,是在异步任务有告终果以后将异步任务的回调函数放到任务队列里等待主线程空闲时执行。注意这里是在执行栈中若是遇到异步任务,是注册异步任务的回调函数 ,把回调函数放到任务队列中(
这一步会产出任务
)。
事件循环的进程模型
总结一下总体的流程:
主线程首先会执行一个宏任务,当此宏任务执行完以后,会去查看是否有微任务队列。若是有,那就清空整个微任务队列。若是没有,会去查看宏任务队列,取宏任务队列中的第一个去执行,执行宏任务的过程当中遇到微任务,依次加入微任务队列等待下次执行。(
固然执行微任务的时候也会产生宏任务,主线程会放入宏任务队列
)。栈空后,再次读取任务队列中的任务,以此类推。
举个例子
console.log('script start')
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve().then(() => {
console.log('promise1')
})
}, 0)
new Promise(resolve => {
console.log('promise2');
setTimeout(() => {
console.log('setTimeout2');
resolve();
}, 0)
})
Promise.resolve().then(() => {
console.log('promise3');
setTimeout(() => {
console.log('setTimeout 3')
},0)
}).then(() => console.log('promise4'))
console.log('script end');
复制代码
输出结果:
script start
promise2
script end
promise3
promise4
setTimeout1
promise1
setTimeout2
setTimeout3
复制代码
详细执行流程以下:
1. 初始宏任务队列只有总体script,执行栈、微任务队列皆为空
宏任务script入栈执行:
输出
script start
碰见
setTimeout1
放入宏任务队列执行同步代码输出
promise2
,碰见setTimeout2
放入宏任务队列碰见微任务
promise3
、promise4
放入微任务队列输出
script end
咱们看下第一个宏任务执行完的状态:
宏任务队列: setTimeout1
、setTimeout2
微任务队列: promise3
、 promise4
2. 接下来去清理微任务队列
微任务队列遵循先进先出原则,
promise3
入栈执行:输出promise三、
,将setTimeout3
放入宏任务队列。promise3
出栈 ,promise4
入栈执行,输出promise4
清理完微任务后的状态:
宏任务队列:setTimeout1
、setTimeout2
、setTimeout3
微任务队列:空
3. 从宏任务队列中取出第一个入栈执行
setTimeout1
入栈执行,输出setTimeout1
,将promise1
放入微任务队列
本次执行完的状态:
宏任务队列: setTimeout2
、setTimeout3
微任务队列: promise1
4. 清理微任务队列
输出
promise1
本轮执行完状态:
宏任务队列: setTimeout2
、setTimeout3
微任务队列: 空
五、 从宏任务队列中取出第一个执行
输出
setTimeout2
本轮执行完状态:
宏任务队列: setTimeout3
微任务队列: 空
6. 微任务队列为空,继续从宏任务队列取出第一个执行
输出
setTimeout3
至此宏任务队列
和微任务队列
都为空。
从上述执行过程当中, 不难看出,浏览器的Event Loop 老是一个宏任务 ——> 一队微任务 这种顺序依次循环
另外一个例子
var a = async function () {
await Promise.resolve().then(() => console.log(111));
console.log(222)
}
a().then(() => console.log(333))
var b = async function () {
await setTimeout(() => console.log('aaa'), 0);
console.log('bbb')
}
b().then(() => console.log('ccc'))
var c = async function () {
await console.log('A');
console.log('B')
}
c().then(() => console.log('C'))
复制代码
输出结果:
A
->111
->bbb
->B
->222
->ccc
->C
->333
->aaa
执行顺序:
1. script入栈,
执行同步代码,调用a():将111放入微任务队列。继续往下走执行b(),将aaa 放入宏任务队列,将 bbb 放入微任务队列,接着执行c(), 打印
A
, 将B
放入微任务队列。
这次执行完的状态:
宏任务队列:
aaa
微任务队列:
111
、bbb
、B
2. 清理微任务队列:
执行 111, 打印
111
、将222
放入微任务队列,执行bbb
:打印bbb
并将ccc
放入微任务队列,执行B
: 打印B
并将C
放入微任务队列,此时微任务队列并无清理完,因此接着打印222
、ccc
、C
,打印完222
将333
放置微任务队列,因此接着打印333
。
这次执行完的状态:
宏任务队列:
aaa
微任务队列: 空
aaa
上述代码如果很差理解能够转化为promise的方式来理解,效果以下:
new Promise(resolve => {
Promise.resolve()
.then(() => console.log(111))
.then(() => {
console.log(222)
resolve()
})
})
.then(() => console.log(333))
new Promise(resolve => {
setTimeout(() => console.log('aaa'), 0)
Promise.resolve().then(() => {
console.log('bbb')
resolve()
})
})
.then(() => console.log('ccc'))
new Promise(resolve => {
console.log('A')
Promise.resolve()
.then(() => {
console.log('B')
resolve()
})
})
.then(() => console.log('C'))
复制代码
但这纯属我的理解,如有问题,还请大神指导。
1.简介
Node 中的 Event Loop和浏览器中的是彻底不相同的东西。Node.js采用V8做为js的解析引擎,而I/O处理方面使用了本身设计的libuv
,libuv
是一个跨平台、专门写给nodejs的库,使用异步,事件驱动的编程方式,核心是提供I/O的事件循环和异步回调。封装了不一样操做系统一些底层特性,对外提供统一的API。
2.六个阶段
未完待续...