图解JavaScript事件循环、执行栈、任务队列、宏任务、微任务

事件循环就是Event Loop,是JavaScript 一个特殊的地方。特殊就在于JavaScript 是单线程语言,注定了对异步操作的处理有别于多线程语言。执行栈和任务队列是JavaScript 执行异步任务的管理方式,宏任务和微任务是js异步任务更细粒度的划分。

单线程

进程和线程是操作系统中的概念,线程是进程的最小单位,一个进程可以包含多个线程,此处不再赘述。JavaScript 在设计之初便是单线程,程序运行时,只有一个线程存在,在特定的时候只能有特定的代码被执行。这和 JavaScript 的用途有关,它是一门浏览器脚本语言,通常是用来操作 DOM 的,如果是多线程,一个线程进行了删除 DOM 操作,另一个添加 DOM,此时该如何处理?所以 JavaScript 在设计之初便是单线程的。

虽然 HTML5 增加了 Web Work 可用来另开一个线程,但是该线程仍受主线程的控制,没有window对象、不能操作DOM,所以 JavaScript 的本质依然是单线程。所有同步、异步任务都是在主线程中执行。

单线程的 JavaScript 一段一段地执行,前面的执行完了,再执行后面的,试想一下,如果前一个任务需要执行很久,比如接口请求、I/O 操作,那后面的任务只能干等吗?干等不仅浪费了资源,而且页面的交互程度也很差。JavaScript 意识到了这个问题,他们将任务分成了同步任务和异步任务,对于二者有不同的处理。

明确什么是同步、异步任务

先看下面这段代码

let name = '小明'                                               //同步任务1
window.addEventListener('popstate', (e) => {     //同步任务2
    console.log(e)      //异步任务
})

这里是两条语句,一是声明name并赋初值,二是添加window的popstate事件的监听。对应的这里就产生了2个任务,是同步任务,主线程按照代码书写顺序先执行任务1(name声明和赋值),再执行同步任务2(添加window的popstate事件的监听)。监听器回调里要去执行console.log(e),所以这条语句也产生了一个任务,而它是一个异步任务,确切的说整个回调函数产生了一个异步任务。

执行栈、任务队列

首先,执行栈管理同步任务、任务队列管理异步任务。同步任务没啥,异步任务分为异步宏任务和异步微任务
常见的宏任务有setTimeout、setInterval;
常见的微任务有 Promise、nextTick(node.js 环境)。
下面就根据代码来分析事件执行顺序:

console.log(1)     //同步任务A

setTimeout(        //同步任务B
   () => {   console.log(2)   }     //任务B产生的异步宏任务
, 300)

new Promise(      //同步任务C
    (resolve) => {   console.log(3);  resolve(4);  }
 )
.then(     //任务C执行过程中resolve(4)语句产生的异步微任务
     (num) => {   console.log(num)   }
)

setTimeout(      //同步任务D
   ()=> { console.log(5)  }     //任务D产生的异步宏任务
, 800)

下面用一张张图片画出各个阶段发生的事情。


图一:执行同步任务B时发生的事

图一:JavaScript 在执行时,同步任务会排好队,在主线程上按照顺序执行A!
完了B,B完了C,直到执行栈里再无任务),排队的地方叫执行栈(execution context stack)。执行A没啥问题,执行到B时,B是一个setTimeout任务,我们知道它会产生一个异步任务,但是没有立刻产生,而是等待300ms再产生个异步宏任务,这个异步任务的目的是去打印数字2。这个时候主线程没有傻傻的等300ms再去打印数字2,而是将任务挂起,继续往下走去执行同步任务C。

图二:执行同步任务C时发生的事

图二:主线程继续往下执行了同步任务C,这个时候同步任务C有两部分,一是console.log(3),二是resolve(4) ,所以先后执行了console.log(3)resolve(4),我们知道resolve(4)执行完会返回promise对象,而promise对象里有自己的执行代码(这段代码里就是console.log(num) ),那么主线程是继续往下执行下一个同步任务?还是转到执行resolve(4)返回的promise对象中的代码呢?答案很明显,会执行下一个同步任务。这个时候resolve(4)所返回的promise对象就产生了一个异步微任务,主线程将它挂起,挂起的方法就是将它塞到异步微任务执行队列中,先不管他。
图三:执行同步任务D时发生的事

图三:又是一个setTimeout,和上一个一样,只不过这个要等800ms才会产生一个异步宏任务。回头看看之前B产生的那个异步宏任务BB去哪儿了呢?别急,300ms还没到呢,主线程从A一路往下到D这个期间异步任务BB还没创建好呢,创建好后主线程会再把它挂起来。这个时候你可能有疑惑,我去,如果B那里设置延时不是300ms,而是3ms或者0呢?岂不是执行顺序会根据电脑性能乱掉?这里如果将 setTimeout 的第二个参数设置为 0,它表示主线程空闲之后尽早执行它的回调,HTML5 规定 setTimeout 的第二个参数不得小于 4 毫秒。
图四:300ms时间到,异步宏任务BB被创建成功

图四:300ms时间到,异步宏任务BB被创建成功,但也没有立即执行,而是被主线程塞到了宏任务执行队列中。
图五:执行栈空了,就把微任务执行队列中任务全部执行完

图五:执行栈空了,就把微任务执行队列中任务全部执行完。
图六:从宏任务执行队列中取出任务放到执行栈继续执行

图六:从宏任务执行队列中取出任务放到执行栈继续执行,此时异步宏任务DD执行也创建好了,被塞到了宏任务执行队列,等待下次被取出放到执行栈执行。
图七:执行完一个宏任务后,再去执行所有的微任务

图七:执行完一个宏任务后,再去执行所有的微任务,微任务执行完后再取出
最后一个宏任务DD放到执行栈执行。后面每执行完一个宏任务后都去“照顾”一下微任务。直到所有的宏任务、微任务都执行完。
所以上面代码的打印顺序是1->3->4->2->5。

总结

总结JavaScript处理同步异步任务的方式是:用栈和队列调度分配任务,代码首次运行主线程按照同步任务顺序,依次执行,执行期间产生的异步宏任务则挂起不执行,挂起的方式是添加至宏任务执行队列,产生的微任务也挂起,方式是添加至微任务执行队列。待所有同步任务执行完成,主线程从宏任务执行队列取出最先添加进宏任务执行队列的任务,放入执行栈执行,执行完后立即去执行所有的微任务,微任务清空后再执行下一个宏任务,每执行完一个宏任务就去清空一下微任务,直到两个执行队列全清空。如此循环就是Event Loop事件循环。

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

推荐阅读更多精彩内容