JavaScript事件循環(huán)剖析宏任務(wù)與微任務(wù)
前言
相信對于剛學(xué)習(xí)JavaScript的新手來說,去理解JS中的事件循環(huán)原理以及異步執(zhí)行過程比較困難,但是這是JS必須要會的基礎(chǔ)知識,逃避不能解決問題,筆者曾經(jīng)也被這個知識點困擾過,現(xiàn)根據(jù)以往的經(jīng)驗編寫此文章,旨在幫助大家徹底搞懂它們以及自我鞏固,話不多說,進入正題。
注意:本篇文章主要是基于瀏覽器環(huán)境,Node環(huán)境沒有研究過暫不討論
引言
我們先來小試牛刀,看看下面這段代碼是怎么執(zhí)行的,例1:
setTimeout(() => { console.log('time') }); new Promise((resolve, reject) => { console.log('p1'); resolve(); }).then(() => { console.log('res') }); console.log(1); // 輸出: p1 1 res time
怎么樣?你想的輸出結(jié)果和實際的輸出結(jié)果是一樣的嗎?如果是一樣的說明你對事件循環(huán)有一定的了解,但是你真的已經(jīng)清楚的知道了事件循環(huán)的原理嗎?讓我們繼續(xù)往下看。
為什么會有事件循環(huán)?
JS是單線程的
眾所周知:JavaScript 是一門單線程語言,也就是說,同一個時間只能做一件事。這是因為 Javascript 這門腳 本語言誕生的使命所致——JavaScript 是為處理頁面中用戶的交互,以及操作 DOM 而誕生的。比如我們對 某個 DOM 元素進行添加和刪除操作,不能同時進行。 應(yīng)該先進行添加,之后再刪除。
單線程就意味著,所有任務(wù)需要排隊,前一個任務(wù)結(jié)束,才會執(zhí)行后一個任務(wù)。這樣所導(dǎo)致的問題是: 如果 JS 執(zhí)行的時間過長,這樣就會造成頁面的渲染不連貫,導(dǎo)致頁面渲染加載阻塞的感覺
為了解決這個問題,利用多核 CPU 的計算能力,HTML5 提出 Web Worker 標準,允許JavaScript 腳本創(chuàng)建多個線程,但是子線程完全受主線程控制。于是,JS 中出現(xiàn)了同步任務(wù)和異步任務(wù)。
同步任務(wù)和異步任務(wù)
- 同步任務(wù):
同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧。在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能執(zhí)行后一個任務(wù);
- 異步任務(wù):
不進入主線程、而進入”任務(wù)隊列”的任務(wù),當主線程中的任務(wù)運行完了,才會從”任務(wù)隊列”取出異步任務(wù)放入主線程執(zhí)行。JS 的異步是通過回調(diào)函數(shù)實現(xiàn)的。異步任務(wù)相關(guān)回調(diào)函數(shù)添加到任務(wù)隊列中(任務(wù)隊列也稱為消息隊列)
注意:異步任務(wù)執(zhí)行機制在這里描述的比較籠統(tǒng),主要方便大家理解,具體細節(jié)在后面的“宏任務(wù)與微任務(wù)”中會詳細介紹
JS的事件循環(huán)就是基于同步任務(wù)與異步任務(wù)來展開的,讓我們繼續(xù)往下看:
JS事件循環(huán)
事件循環(huán)是JavaScript實現(xiàn)異步的一種方法,也是JavaScript的執(zhí)行機制
如圖:
當一個腳本第一次執(zhí)行的時候,js引擎會解析這段代碼,并將其中的同步代碼按照執(zhí)行順序加入執(zhí)行棧中,然后從頭開始執(zhí)行。
當遇到異步任務(wù)時不會一直等待事件的返回結(jié)果,而是將事件掛起(即交給其他線程處理,上圖是指Web Worker),繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。
當異步事件返回結(jié)果時,js將異步事件callback函數(shù)放入隊列中,被放入隊列中的異步事件不會立即回調(diào),等到當前執(zhí)行棧中的任務(wù)都執(zhí)行完成,處于閑置狀態(tài)的主線程按照隊列順序?qū)⑻幱谑孜皇录腸allback函數(shù)放入執(zhí)行棧中,執(zhí)行該函數(shù)的同步代碼,如果遇到了異步事件,同樣也會將其回調(diào)函數(shù)放入事件隊列中…
如此反復(fù),就形成了一個循環(huán),這也是被稱為“事件循環(huán)(EventLoop)”的原因。
js事件循環(huán)的基本原理已經(jīng)描述清楚,但是異步任務(wù)之間也有所不同:
任務(wù)隊列實際上分為兩個:宏任務(wù)隊列和微任務(wù)隊列。上圖只表示了一個是為了便于大家理解事件循環(huán),下面就是事件循環(huán)更細節(jié)的東西了
宏任務(wù)與微任務(wù)
上面講到,js在執(zhí)行異步任務(wù)時,回調(diào)函數(shù)會被放在js的任務(wù)隊列中,實際上,回調(diào)函數(shù)的類別不同,執(zhí)行的優(yōu)先級也不同。
不同的優(yōu)先級被分為兩類,一類是宏任務(wù)(Micro task),一類是微任務(wù)(Macro task)。
回調(diào)函數(shù)是微任務(wù)時,會被放在微任務(wù)隊列,回調(diào)函數(shù)是宏任務(wù)時,會被放在宏任務(wù)隊列。
微任務(wù)的優(yōu)先級高于宏任務(wù),當主線程的任務(wù)執(zhí)行完成時,會首先去執(zhí)行微任務(wù)隊列中首位的回調(diào)函數(shù),當微任務(wù)隊列中為空時,才回去執(zhí)行宏任務(wù)隊列中的回調(diào)函數(shù)。
常見的宏任務(wù)有哪些?
- 包括整體代碼 script
- setTimeout()
- setInterval()
- setImmediate()(Node獨有)
- I/O
- UI 交互事件(瀏覽器獨有)
- requestAnimationFrame() (瀏覽器獨有)
常見的微任務(wù)有哪些?
- Promise.then(); Promise.cath()
- async/await
- process.nextTick() (Node獨有)
- MutationObserver() (H5新增,監(jiān)聽DOM樹變化)
- Object.observe() (異步監(jiān)視對象修改,已廢棄)
注意:new Promise()屬于同步任務(wù),但是Promise.then(); Promise.cath()屬于異步任務(wù)的微任務(wù)
執(zhí)行過程總結(jié)(重點)
現(xiàn)在我們對事件循環(huán)有了深入了解了,但是它們的執(zhí)行過程還不是很清晰,我們再把執(zhí)行過程弄清楚了以后就能游刃有余了。
同步任務(wù) —> 微任務(wù) —> 宏任務(wù)...
- 先執(zhí)行所有同步任務(wù),碰到異步任務(wù)放到任務(wù)隊列中
- 同步任務(wù)執(zhí)行完畢,開始執(zhí)行當前所有的異步任務(wù)
- 先執(zhí)行任務(wù)隊列里面所有的微任務(wù),如果執(zhí)行過程中又產(chǎn)生了微任務(wù)也會在本次執(zhí)行過程中執(zhí)行(即在下一個宏任務(wù)執(zhí)行之前執(zhí)行,可以看看案例1)
- 然后執(zhí)行一個宏任務(wù)(從宏任務(wù)隊列頭部pop出一個宏任務(wù)進執(zhí)行棧,該任務(wù)中的具體代碼也如步驟1執(zhí)行)
- 然后再執(zhí)行所有的微任務(wù)(此時的微任務(wù)一般為步驟4中產(chǎn)生出的微任務(wù))
- 再執(zhí)行一個宏任務(wù),再執(zhí)行所有的微任務(wù)·······依次類推到執(zhí)行結(jié)束。
3-6的這個循環(huán)稱為事件循環(huán)Event Loop
案例挑戰(zhàn)
學(xué)會了嗎?讓我們來做幾個案例鞏固一下吧
案例1:
const promise = new Promise((resolve, reject) => { resolve("10") }).then(res => { console.log("res1:", res) //res1: hahaha return 9 }).then(res => { console.log("res2:", res) //res2: 9 return 8 }).then(res => { console.log("res3:", res) //res3: 8 let promise2=new Promise((resolve,reject)=>{ resolve("p2") }).then(res=>{ console.log(res) setTimeout(function(){ console.log("setTimeout2") },0) }) }) console.log('aaa') setTimeout(function(){ console.log("setTimeout1") },0) const promise1 = new Promise((resolve, reject) => { console.log("p1") resolve(989) }).then(res => { console.log(res) return 990 }).then(res=>{ console.log(res) return 991 }).then(res=>{ console.log(res) return 0 }) /*輸出結(jié)果: aaa p1 res1: 10 989 res2: 9 990 res3: 8 991 p2 setTimeout1 setTimeout2 */
案例2:
console.log('1'); // 定義注解 setTimeout_1 用于下文使用方便 setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') }) }) process.nextTick(function() { console.log('6'); }) new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) // setTimeout_2 setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) }) // 輸出結(jié)果: 1 7 6 8 2 4 3 5 9 11 10 12
案例3:
console.log('1'); setTimeout(function() { console.log('2'); process.nextTick(function() { console.log('3'); }) new Promise(function(resolve) { console.log('4'); resolve(); }).then(function() { console.log('5') }) }) process.nextTick(function() { console.log('6'); }) new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) }) // 輸出結(jié)果:1 7 6 8 2 4 3 5 9 11 10 12
以上就是JavaScript事件循環(huán)剖析宏任務(wù)與微任務(wù)的詳細內(nèi)容,更多關(guān)于JavaScript 事件循環(huán)宏任務(wù)微任務(wù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
javascript中動態(tài)加載js文件多種解決辦法總結(jié)
這篇文章主要介紹了javascript中動態(tài)加載js文件多種解決辦法,有需要的朋友可以參考一下2013-11-11js動態(tài)獲取子復(fù)選項并設(shè)計全選及提交的實現(xiàn)方法
下面小編就為大家?guī)硪黄猨s動態(tài)獲取子復(fù)選項并設(shè)計全選及提交的實現(xiàn)方法。小編覺得挺不錯的, 現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-06-06mpvue微信小程序多列選擇器用法之省份城市選擇的實現(xiàn)
這篇文章主要給大家介紹了關(guān)于mpvue微信小程序多列選擇器用法之省份城市選擇實現(xiàn)的相關(guān)資料,文中通過示例代碼以及圖文介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03javascript 用函數(shù)語句和表達式定義函數(shù)的區(qū)別詳解
本篇文章主要介紹了javascript 用函數(shù)語句和表達式定義函數(shù)的區(qū)別。需要的朋友可以過來參考下,希望對大家有所幫助2014-01-01純javascript實現(xiàn)四方向文本無縫滾動效果
本文主要給大家分享了使用純javascript實現(xiàn)的可控制的四方向文本無縫滾動的代碼,效果非常不錯,有需要的小伙伴可以參考下。2015-06-06