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