javascript事件循环分为2种:一种是浏览器端事件循环,一种是node端事件循环。 此文只是捋一捋我对浏览器端事件循环的理解。
前言
我们都知道 JavaScript 是一门单线程语言,这意味着同一时间只能执行一个任务,结束了才能去执行下一个。如果前面的任务没有执行完,后面的任务就会一直等待。试想,有一个耗时很长的网络请求,如果所有任务都需要等待这个请求完成才能继续,显然是不合理的并且我们在浏览器中也没有体验过这种情况(除非你要同步请求 Ajax),究其原因,是 JavaScript 借助异步机制来实现了任务的调度。
JavaScript 常见的异步有3种:ajax、dom事件(button的点击事件等)、定时器(setTimeout等)。
举个例子
console.log("A");
setTimeout(() => {
console.log("B");
}, 100);
console.log("C");
稍微了解一点浏览器中异步机制的同学都能答出会输出 “A C B”,我们来通过分析 event loop 来对浏览器中的异步进行梳理,并搞清上面的问题。
从上图我们可以看出js分为3个部分:stack(主线程)、WebAPIs、event loop。
浏览器的执行顺序是:
1.先执行主线程,即
console.log("A");
setTimeout(() => {
console.log("B");
}, 100);
console.log("C");
程序从上往下执行,但是执行到setTimeout时,只是注册了一个WebAPIs事件,并不会执行setTimeout的回调函数。 此回调函数是异步的,会在主线程的所有程序执行完毕后根据注册的时间才执行。
2.当主程序执行完毕后,就执行event loop。当然callback queue一开始是空的。event loop是一个‘死循环’,它会一遍又一遍的去检测WebAPIs有没有可执行的回调函数,有就把回调函数放入自身的callback queue并执行,没有的话继续下一次循环。
3.浏览器监听WebAPIs事件。过了100ms定时器事件触发了,它会通知event loop有可执行的回调函数了。
事件循环的一个必须条件是 执行栈中的任务执行完毕才会执行 “任务队列”中的任务,如果执行栈一直处于执行不完的情况,也就没有任务队列什么事了。所以我们的settimeout 后面的参数其实是不准确的,他开始算的时间应该是执行栈的任务执行完毕。
settimeout还有一个最小时间4ms的规定,所以当我们写settimeout(function(){},0)时,其中的0其实是4ms。
微任务和宏任务
首先说明,是以__浏览器为处理环境__下的执行逻辑 浏览器环境下的微任务和宏任务有哪些 宏任务:setTimeout setImmediate MessageChannel 微任务:Promise.then MutationObserver 记住两点:
- 微任务在宏任务之前的执行,先执行 执行栈中的内容 执行后 清空微任务
- 每次取一个宏任务 就去清空微任务,之后再去取宏任务
题目
setTimeout(function(){
console.log('setTimeout1');
Promise.resolve().then(()=>{
console.log('then1');
});
},0)
Promise.resolve().then(()=>{
console.log('then2');
Promise.resolve().then(()=>{
console.log('then3');
})
setTimeout(function(){
console.log('setTimeout2');
},0)
})
- 微任务宏任务同时出现,先将微任务放入执行队列,栈为空则先执行then2。setTimeout1放入宏任务执行队列中
- then2之后执行后,接下来存在微任务then3。将then3放入微任务队列中。
- 接下来setTimeout2加入到宏任务队列中。
- 此时执行栈为空,执行then3。
- 微任务全部执行完毕后,执行宏任务setTimeout1,执行发现微任务then1,放置到微任务队列中。
- setTimeout1宏任务执行完,再次清空微任务队列,执行then1
- 微任务全部执行完毕后,执行宏任务setTimeout2。程序结束。