JS事件循环
laizimo opened this issue · 1 comments
涉及到多个异步事件执行的时候,大家会不会产生思考!执行顺序,还和原来一样么?还是有什么固定的规则,让我们去判断!今天我们就来了解一下Event Loop的概念。
在聊起这个概念之前,我们先来听一段JavaScript语言的自述。
浏览器为什么选择了我
众所周知,JavaScript是一门单线程的语言。为什么浏览器会去选择它呢!原因就是——简单。在浏览器端,复杂的UI环境会限制多线程语言的开发。例如:
一个线程在操作一个DOM元素的时候,另一个线程需要去删除这个DOM元素。这种情况下,我们就需要进行状态的同步。可怕的是,浏览器往往不止去操作一个DOM元素!所以,为了避免开发中处理这种复杂的情况,单线程语言不失为一种好的解决方案。
但是,单线程也会有它的缺陷——同步阻塞。如图所示:
CPU在进行一个I/O操作的时候,需要去请求数据,期间需要等待数据返回之后,才能够继续执行下面的任务。这个等待期,就阻塞了其他任务的执行。因此,JavaScript在执行过程中,将任务分成了同步任务和异步任务,来解决类似的情况。
同步/异步
每个线程都有一个执行栈,会根据先进后出的顺序来执行线程中的任务,所有的同步任务,都会被放到这个执行栈中,我们可以来看一段代码:
function fun1(){
return 'hello hip-hop';
}
function fun2(){
return fun1();
}
function fun3(){
console.log(fun2());
}
fun3(); //'hello hip-hop'
它的执行顺序如下:
或者我们可以通过浏览器后台的报错来看整个执行顺序,如下:
function fun1(){
throw new Error('hello hip-hop');
}
function fun2(){
return fun1();
}
function fun3(){
console.log(fun2());
}
fun3();
浏览器后台的报错提示,如图:
在此基础上,我们如果加入异步任务,会发生什么样的情况呢,如下:
console.log('first');
setTimeout(() => {
console.log('second');
}, 500);
console.log('three');
我们依然通过画图的形式,来直观地感受一下执行栈的顺序,如图:
从图中,我们可以清晰地看到setTimeout执行完成之后,就出栈了!那么,后来的console.log('second')是如何入栈的呢?
其实,在主线程之外,还存在一个任务队列。异步任务,都会被放到任务队列中。只有当指定事件触发之后,异步任务才会被放到主线程中执行。
任务队列中,是一个事件队列。拿setTimeout举例来说:
- 当主线程执行到setTimeout的时候,会创建一个定时器;
- 一旦定时器的到达时间,就会将回调函数放到任务队列中;
- 当主线程任务执行完成之后,就会去循环任务队列,执行回调函数;
事件循环
上述流程,规范为一张图如下:
这里有个循环,是一个死循环,无论哪种情况都是闭环,这个就是事件循环。事件循环不断地在检测队列是否存在已触发的任务,如果有的话,就放到主线程中执行(注:这个过程往往在主线程执行完之后进行)。
这幅图里面,我们看到了有浏览器的点击事件、ajax请求、Promise等这里。但是不同之处在于,它们的任务性质存在不同。
自从,ES6出现之后,Promise逐渐被开发者热议。这里我们来讨论一下它这方面的特殊性。
首先,我们来看一段代码:
setTimeout(() => {
console.log(1);
}, 0);
Promise.resolve().then(() => {
console.log(2);
}).then(() => {
console.log(3);
});
console.log(4); // 4 2 3 1
你会不会有所疑问,为啥不是4 1 2 3的顺序呢?
其实,这个执行顺序和任务队列有关系!任务队列中存在两种队列类型:宏任务和微任务。宏任务可以有多个队列,微任务只能有一个!同时,宏任务是一个一个出队的,而微任务是一队一队出队的。
在执行事件循环的过程中:
- 主线程会先遍历一遍微任务队列,然后将队列中的函数抽离出来执行。
- 执行完成之后,再执行一个宏任务,在循环一遍微任务队列,再执行一个宏任务
- 直至队列都循环完毕。
了解清楚这个后,我们再回头看,心中亦如明镜。setTimeout是宏任务,Promise是微任务。具体分类如下:
- 宏任务:setTimeout,setInterval,JavaScript(整段代码),I/O操作,UI渲染等
- 微任务:Promise,process.nextTick(NodeJS)等
总结
本文我们回顾了:
- JS的线程机制,同步和异步操作
- 事件循环的过程
- 任务队列的不同
欢迎您扫一扫上面的微信公众号,订阅我的博客!