事件环介绍
event loops也就是事件循环,它是为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking),用户代理(user agent)的工作而产生的一个机制。
一.浏览器中事件环
我们都知道javascript是单线程的,任务是需要一个一个按顺序执行的,如果javascript有两个线程,一个为DOM增加样式,一个却要删除DOM,这样岂不是就会很混乱。单线程可以节约内存,但是必须等待前一个任务完成后才能执行下一个任务。如上图,浏览器中的事件环图,图中包含有:
-
heap(堆):用户主动请求而划分出来的内存区域,比如你new一个对象,就是将一个对象存入堆中,可以理解为heap存对象。
-
stack(栈):由于函数运行而临时占用的内存区域,函数都存放在栈里。
-
Event Loop:
-
所有同步任务都在主线程上执行,形成一个执行栈。
-
只要异步任务有了运行结果,就在任务队列(task queue)(队列是一个先进先出的数据结构,而栈是一个先进后出的数据结构)之中放置一个事件
-
一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,又将队列中的事件放到stack中依次执行,就是执行异步任务中的回调函数。这个过程是循环不断的,这就是Event Loop(事件循环);
-
-
宏任务(MacroTask)和微任务(MicroTask)
- 浏览器中宏任务(MacroTask)方法有:setTimeout setInterval
- 浏览器中微任务(MicroTask)方法有:Promise.then MessageChannel Promise.then MessageChannel
执行过程
* 执行完主执行线程中的任务。
* 取出Microtask Queue中任务执行直到清空。
* 取出Macrotask Queue中一个任务执行。
* 取出Microtask Queue中任务执行直到清空。
* 重复第三和第四步。
复制代码
举例说明:
console.log(1);
setTimeout(function(){
console.log('settimeout1');
new Promise(function(resolve,reject){
console.log('promise');
resolve();
}).then(res=>{
console.log('promise.then');
})
});
setTimeout(function(){
console.log('settimeout2');
})
console.log(2);
复制代码
在浏览器中输出结果顺序为:
1
2
settimeout1
promise
promise.then
settimeout2
复制代码
分析:
执行栈中同步任务先执行,先走console.log(1)和console.log(2);
接着是遇到setTimeout将它们的回调函数放入MacroTask(宏任务队列);
然后将任务队列中的回调函数依次放入主执行栈中执行,console.log('settimeout1'),接着promise是立即执行,promise.then是微任务放入MicroTask中先执行;
最后执行第二个setTimeout的回调函数console.log('settimeout2');
二.node.js中事件环
当Node.js启动时,它会初始化Eventloop,处理提供的输入脚本(可能会发出异步API调用),然后开始处理Event loop。只有一个线程,那就是Event循环运行的线程。事件循环以循环顺序工作,具有不同的阶段。Node中 Event loop的操作顺序如上所示。- 定时器(timers)这个阶段执行定时器队列中的回调如 setTimeout() 和 setInterval()。
- I/O回调(I/O callbacks)这个阶段执行几乎所有的回调。但是不包括close事件,定时器和setImmediate()的回调。
- 闲置,准备(idle,prepare) 这个阶段仅在内部使用,可以忽略。
- 轮询(poll) 等待新的I/O事件,node在一些特殊情况下会阻塞在这里
- 检查(check)setImmediate()的回调会在这个阶段执行
- 宏任务(MacroTask)和微任务(MicroTask)
- 关闭回调(close callbacks):关闭所有的closing handles,一些onclose事件。
执行过程
-
在进入第一次循环之前,会先进行如下操作:1. 同步任务2.发出异步请求3.规划定时器生效的时间4.执行process.nextTick()
-
清空当前循环内的Timers Queue(执行满足条件的setTimeout、setInterval回调),清空NextTick Queue,清空Microtask Queue。
-
清空当前循环内的I/O Queue(执行I/O操作的回调函数),清空NextTick Queue,清空Microtask Queue。
-
清空当前循环内的Check Queue(执行setImmediate的回调),清空NextTick Queue,清空Microtask Queue。
-
清空当前循环内的Close Queue,清空NextTick Queue,清空Microtask Queue。
-
进入下一循环
由此可见:nextTick优先级比promise等microtask高。setTimeout和setInterval优先级比setImmediate高。
复制代码
举例说明:浏览器中的Event Loop和node的Event Loop有所不同,将上面同样的示例代码在node环境中运行
console.log(1);
setTimeout(function(){
console.log('settimeout1');
new Promise(function(resolve,reject){
console.log('promise');
resolve();
}).then(res=>{
console.log('promise.then');
})
});
setTimeout(function(){
console.log('settimeout2');
})
console.log(2);
复制代码
在node环境中输出结果顺序为:
1
2
settimeout1
promise
settimeout2
promise.then
复制代码
在node环境里,执行栈会先执行完当前任务队列,也就是两个setTimeout中的回调函数执行完才会去执行微任务队列,promise.then会最后执行。