详解事件循环与任务队列

本文首发于我的博客这是我的github,欢迎star。

在网上找了很多事件循环和任务队列相关的文章,有些说的不是很清楚,看完感觉还是有些晕晕乎乎,所以写这篇博客把整体思路梳理一下。如果你有什么不同的理解,或是疑惑的地方,可以留言讨论。这里是我的github,欢迎来访。

事件循环与任务队列是JS中比较重要的两个概念。这两个概念在ES5ES6两个标准中有不同的实现。尤其在ES6标准中,清楚的区分宏观任务队列微观任务队列才能解释Promise一些看似奇怪的表现。

事件循环

事件循环是什么?为什么要有事件循环这个东西?我们都知道JS是单线程的,但是像Ajax,或是DOM事件这种很耗时的操作,需要用并发处理,否则单线程会长时间等待,什么也做不了。而单线程循环就是并发的一种形式,一个线程中只有一个事件循环。而任务队列是用来配合事件循环完成操作的,一个线程可以拥有多个任务队列。

任务队列

任务队列是什么?故名思意,排着任务的队列。所谓任务是WebAPIs返回的一个个通知,让JS主线程在读取任务队列的时候得知这个异步任务已经完成,下一步该执行这个任务的回调函数了。主线程拥有多个任务队列,不同的任务队列用来排列来自不同任务源的任务。任务源是什么?像setTimeout/Promise/DOM事件等都是任务源,来自同类任务源的任务我们称它们是同源的,比如setTimeout与setInterval就是同源的。在ES6标准中任务队列又分为宏观任务队列微观任务队列,我们后边再详细讨论。

下面先通俗的讲述一下ES5中事件循环到底是怎么循环的,如图(据阮一峰前辈的教程):

事件循环1

图中有三大块:

  • 函数调用栈:即执行栈。
  • WebAPIs:浏览器的接口。比如一个Ajax操作,主线程会把收发Ajax交给浏览器的API,之后就继续做别的事情,浏览器在接收到Ajax返回的数据之后,会把一个Ajax完成的事件排到相应的任务队列后边。
  • 任务队列们:主线程中有多个任务队列,同源的任务排在属于自己的任务队列。

一个具体点的栗子。比如现在打开了一个页面,里边有一段<script>,其中有AjaxDOM操作等等。这段JS是在浏览器提供的全局环境(浏览器中是window)里执行的,执行中遇到函数调用时会压入执行栈。

  1. 主线程在遇到Ajax或是setTimeout这种异步操作时会交给浏览器的WebAPIs,然后继续执行后边的代码,直到最后执行栈为空。
  2. 浏览器会在不确定的时间将完成的任务返回,排到相应的任务队列后。
  3. 执行栈为空后,主线程会到任务队列中去取任务,这些任务会告诉下一步应该执行哪些回调函数。任务队列是具有优先级的,按照优先级决定访问的先后顺序。而优先级在不同的环境中会有所不同,所以不能给出一个固定的优先级。
  4. 每访问一个队列,执行栈会执行完这个任务队列的所有的代码,然后再取下一个任务队列需要执行的的代码。如果在执行中遇到了当前属于任务队列的异步任务时。此次任务的返回不会直接排到当前任务队列之后。因为这属于两次不同的事件循环,会被区分开来。

就这样循环执行,直到三大块全为空,这称为事件循环

微观任务队列

ES6标准中任务队列存在两种类型,一种就是上边提到的一些队列,比如setTimeout、网络请求Ajax、用户I\O等都属于宏观任务队列(macrotask queue),另一种是微观任务队列(microtask queue),Promise就属于微观任务队列。
  添加了微观任务队列之后事件循环有什么变化呢?在执行栈执行的过程中会把属于微观任务队列的任务分配到相应的微观任务队列中去。而在调用栈执行空之后,主线程读取任务队列时,会先读取所有微观任务队列,然后读取一个宏观任务队列再读取所有的微观任务队列。如图:

事件循环2

好了,说了很多概念上的东西,不如一段代码来的清晰:

setTimeout(function(){console.log(4)},0);
new Promise(function(resolve){
    console.log(1)
    for( var i=0 ; i<10000 ; i++ ){
        i==9999 && resolve()
    }
    console.log(2)
}).then(function(){
    console.log(5)
});
console.log(3);
  • 脚本开始执行,最先遇到setTimeout,交给浏览器去计时,达到setTimeout限制最短计时之后,把这个任务推入setTimeout队列。
  • 遇到Promise构造函数,构造函数参数执行,输出1,调用resolve改变Promise对象的状态,然后输出2
  • Promise对象调用then方法,将这个任务推入Promise任务队列。
  • 执行console.log(3),输出3
  • 调用栈为空,读取任务队列,按照
    读取所有微观任务队列 -> 执行 ->
    读取一个宏观任务队列 -> 执行 ->
    读取所有微观任务队列 -> 执行 ->
    再读取一个宏观任务队列...的顺序。
  • 读取所有微观任务队列中的任务,执行这些任务指定的回调函数。执行then指定的回调函数,输出5(微观任务队列也具有优先级)。
  • 最后读取到setTimeout的任务,执行回调函数,输出4

所以最后的输出顺序是1,2,3,5,4,而不是1,2,3,4,5。如果不清楚微观任务队列的执行机制,很容易将两个异步任务归为一类,将执行顺序判断错误。

到这里算是把事件循环和任务队列说的比较清楚了,参考了很多大佬的博客与讨论:
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
https://www.zhihu.com/question/36972010
http://www.jianshu.com/p/12b9f73c5a4f
http://www.cnblogs.com/hity-tt/p/6733062.html

如果你有不同的理解请到博客下方留言,这是我的github,欢迎来访,你的star就是我的动力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容