本文主要讲解JavaScript的运行机制,对事件循环(Event Loop)进行深入研究。
JavaScript运行机制、事件循环
一、为什么js要是单线程
如果是多线程的话,有2个线程,同时对同一个DOM节点进行修改:一个要编辑该DOM节点的内容,一个要删除该DOM节点,那浏览器就不知道如何执行了。
二、为什么js要异步?
因为同步会阻塞js代码的执行,而异步不会阻塞。如果js不存在异步,那么js只能自上而下执行,当遇到某一处代码解析执行时间很长的时候,那么下面的代码将会被阻塞,对用户而言,阻塞意味着“卡死”,这样就导致很差的用户体验。
三、js如何实现异步?
通过事件循环(event loop)。
四、例子
<script>
console.log(1);
setTimeout(function() { // 宏任务
console.log(2)
}, 100);
setTimeout(function() { // 宏任务
console.log(3);
}, 0);
new Promise(function(resolve, reject) { // 注意new Promise是同步任务(new关键字用来创建对象,属于同步任务)
console.log(4);
for (var i=0;i<10000;i++) {
i == 99 && resolve();
}
console.log(5)
}).then(function(){ // then是异步任务,而且是属于微任务
console.log(6);
}).catch(function() {
console.log(7);
});
console.log(8);
</script>
分析:
运行上面代码会输出:1 4 5 8 6 3 2
- 遇到第一个宏任务
<script>
,第一行console.log(1)属于本轮宏任务,所以马上就执行,输出1。 - 遇到了一个setTimeout,是一个新的宏任务,就把这个setTimeout放到【宏任务队列】中,等一下一轮event loop再来执行。
- 又遇到了一个新的setTimeout,又是一个新的宏任务,同样,把这个setTimeout放到【宏任务队列】中,等待下一轮event loop再来执行。
- 遇到new Promise语句,属于本轮的宏任务,所以马上执行那个function中的内容,所以输出4 5。
- 遇到then,是一个微任务,于是将其加入到【微任务队列】中,等本轮的宏任务全部执行完了再来执行这个微任务。
- 然后遇到console.log(8),也属于本轮的宏任务,所以输出8。自此,本轮的宏任务全部执行完毕。
- 接下来检查本轮的【微任务队列】,发现有一个then的微任务,于是就执行then中的函数,所以输出7. 自此,本轮微任务也全部执行完,开始进入下一轮event loop
- 从这里开始,就是新的一轮event loop,开始执行一个新的宏任务,setTimeout,这里又2个setTimeout,而且等待的时间不同,那会先显示那个呢?答案是:当setTime等待的时间相同时,那么就按进入【队列】的顺序执行;如果等待的时间不同,那么就会先执行【等待时间短】的那个setTimeout的回调。(其实也很好理解,等待时间短的就说明会先完成等待,因此会先执行回调嘛)。所以回到上面的程序,一个setTimeout要等待的是100ms,而另一个到等待0ms,很明显,就是等待0ms的那个setTimeout的回调先回执行,所以先显示3,再显示2。
总结
除了广义的同步任务和异步任务,我们对任务有更精细的定义:
- macro-task(宏任务):包括整体代码script,setTimeout,setInterval
- micro-task(微任务):Promise,process.nextTick
规则:
<script>
一般都是第一个执行的宏任务;在执行宏任务的过程中,若遇到一个新的宏任务,那么就会将这个新的宏任务放到【宏任务队列】中,等下一个event loop再来做;
在执行宏任务的过程中,若遇到一个新的微任务,那么就会将这个新的微任务放到【微任务队列】中,等本轮宏任务做完后,再来处理这个微任务。
做完本轮宏任务后,会去【微任务队列】中,检查是否有微任务,如果有则执行微任务,执行完后,就完成了本轮event loop,进入下一个event loop。
参考文章: