- 理解js的EventLoop原理有利于理解js的执行机制
- 对于初学js的人而言,可能会有这样一个浅显的认识:js的执行,从宏观上来看,就是先执行同步任务,遇到异步任务,先挂起,等到同步任务都执行完了,再去执行异步任务。
- 其实,深入刨析这个过程,其本质就是js的事件循环(EventLoop)
js是单线程的,但为了避免线程阻塞问题即后续任务要等待前面的任务执行完以后再执行,所以程序设计者利用了异步这个功能。
js的内存模型中包括三个内容:
- 堆
- 调用栈(call stack)
- 任务队列(queue)
而所谓事件循环的实质就是
- 在js执行时,首先将同步任务压入执行栈中执行,而将异步操作完成后的事件放入任务队列中,当执行栈中的同步任务执行完成以后,就读取任务队列中的异步任务,开始执行。
- 而这个循环就体现在,不断去任务队列拿到任务,在执行栈执行完后,再去任务队列中拿任务。
在这个过程中,还涉及到一些具体内容:
事实上任务队列,分为宏任务和微任务两种:
宏任务:主体代码 setTimeout() setInterval(定时器)
微任务:promise.then, process.nextTick(),promise.finally。async函数中await后面的代码(当作promse.then()来理解)
执行栈在执行完任务(同步任务或者待执行的异步任务)后,会先查看微任务队列,若微任务队列中还有待执行任务则继续执行,否则,才去查看宏任务队列,看是否有待执行任务。
另外,在异步任务出现的情况下,才会有任务队列的出现,所以对于异步的出现情况,有一些总结:
- dom操作,即各种事件,如onclick()等
- ajax,触发异步回调
- setTimeout()等定时器
这三种情况总会有异步任务
通过代码示例来进一步理解:
eg1:
setTimeout(() => {
console.log('延时1秒');
},1000)
console.log("开始")
这段js的代码执行过程:
-
将宏任务setTimeout() 1秒后的异步任务放到宏任务队列中,执行同步任务,输出“开始”,
-
同步任务执行完后,先查看微任务队列中是否有待处理任务,这里的没有微任务,所以微任务队列为空,然后,再查看宏任务队列,此时宏任务队列中有一个事件即
() => { console.log('延时1秒'); }`
将此事件放入执行栈中执行
-
执行栈执行完后为空,再次去分别查看微任务和宏任务队列,此时都为空,结束。
eg2:
setTimeout(function() {
console.log('setTimeout');
},1000)
new Promise(function(resolve) {
console.log('promise');
}).then(function() {
console.log('then');
})
console.log('console');
过程:
- 1秒后将输出"setTimeout"事件放入宏任务队列
- new Promise()中的为同步任务,直接输出“promise”
- 将promise.then中的任务放入微任务队列
- 执行同步任务,输出“console”,执行栈为空(其实还有一个全局上下文执行环境)
- 查看微任务队列,执行输出“then”,微任务队列为空
- 查看宏任务队列,执行输出“promise”,宏任务队列为空
-
process.nextTick()优先级高于promise.then
idle观察者 > I/O观察者 > check观察者。
idle观察者:process.nextTick
I/O观察者:一般性的I/O回调,如网络,文件,数据库I/O等
check观察者:setImmediate,setTimeout
-
经典优先级示例
setImmediate(function(){
console.log(1);
},0);
setTimeout(function(){
console.log(2);
},0);
new Promise(function(resolve){
console.log(3);
resolve();
console.log(4);
}).then(function(){
console.log(5);
});
console.log(6);
process.nextTick(function(){
console.log(7);
});
console.log(8);
//输出结果是3 4 6 8 7 5 2 1
- 效率
process.nextTick(),效率最高,消费资源小,但会阻塞CPU的后续调用;
setTimeout(),精确度不高,可能有延迟执行的情况发生,且因为动用了红黑树,所以消耗资源大;
setImmediate(),消耗的资源小,也不会造成阻塞,但效率也是最低的。
参考自网络 - 如果多个定时器,进入宏任务队列,也要看定时器时长
比如这种,定时器延时为0的,与定时器延时100的,同时进宏任务队列,也是为0 的先执行有结果
setTimeout(function () {
setTimeout(function () {
console.log(1);
}, 100);
console.log(2);
setTimeout(function () {
console.log(3);
}, 0);
}, 0);
setTimeout(function () {
console.log(4);
}, 100);
console.log(5);
参考:
https://juejin.im/post/59e85eebf265da430d571f89
https://www.cnblogs.com/hanzhecheng/p/9046144.html
https://www.cnblogs.com/xiaohuochai/p/8527618.html