0.前言
又是新的一年啊!
1.簡介
JavaScript 是腳本語言,JS也是單線程的耳高,因此同一時間只能做一件事,也就是說它一次僅能處理一個任務概荷。
思考題
- JavaScript為什么是單線程的? 為什么需要異步? 單線程又是如何實現(xiàn)異步的呢?
- JavaScript中的
event loop()
- 看一段代碼碌燕,想下正確的輸出順序
console.log(1)
Promise.resolve().then(function () {
console.log(2)
})
new Promise(function(resolve, reject){
console.log(3)
resolve()
}).then(function () {
console.log(4)
setTimeout(function () {
console.log(5)
})
})
console.log(6)
setTimeout(function () {
Promise.resolve().then(function () {
console.log(7)
setTimeout(function () {
console.log(8)
})
})
})
2.概念
執(zhí)行上下文(Execution Context)
執(zhí)行上下文簡單來說就是一個執(zhí)行環(huán)境修壕。它有全局環(huán)境、函數(shù)環(huán)境和eval
函數(shù)環(huán)境之分蓝谨。它會在javascript引擎執(zhí)行你的腳本的時候去創(chuàng)建青团。
執(zhí)行棧(Execution Stack)
執(zhí)行棧也就是常說的調(diào)用棧,它是一種擁有LIFO(后進先出)的數(shù)據(jù)結構缕题。它會存儲代碼運行時創(chuàng)建的執(zhí)行上下文
微任務(micro task)與宏任務(macro task)
javasript中的任務分為微任務和宏任務兩種胖腾,這兩種任務的執(zhí)行時機是不同的,因此區(qū)分js中哪些是宏任務锨阿,哪些是微任務則十分重要墅诡。我們常見的宏任務有:script
任務桐智、setTimeout
、ajax
等然磷,常見的微任務比較典型的是:Promise.resolve().then()
刊驴、process.nextTick
寡润、MutationObserver
等梭纹。
事件循環(huán)(event loop)
javasript是單線程的致份,一次僅能處理一個任務。但js所在的宿主環(huán)境瞬沦,也就是我們所說的瀏覽器并不是單線程的(這里宿主環(huán)境僅討論瀏覽器)雇锡。它在遇到一些任務時锰提,比如說setTimeout
芳悲、event listener
等。它會告訴瀏覽器:老兄幫個忙谅年,事成后通知我一聲肮韧,小弟我先干別的事去了。瀏覽器會回應說:交給我吧超燃,小老弟拘领,事成后我放到任務隊列,自己去取啊届良。于是圣猎,javasript開始執(zhí)行script
任務,執(zhí)行完了就開始檢查有沒有微任務啊为障,沒有的話就從任務隊列開始取宏任務執(zhí)行鳍怨,每執(zhí)行完一次宏任務,就去看看有沒有微任務声滥,有的話就執(zhí)行完成侦香,再執(zhí)行宏任務,如此往復憾赁。如下圖:
而準確的劃分方式是:
-
macro-task
(宏任務):包括整體代碼script
龙考,setTimeout
矾睦,setInterval
-
micro-task
(微任務):Promise
,process.nextTick
3.回答思考題
JavaScript為什么是單線程的?
因為現(xiàn)在如果有兩個任務一個是刪除DOM節(jié)點缓溅,一個是增加DOM節(jié)點赁温,瀏覽器該如何執(zhí)行?所以JavaScript是單線程
為什么需要異步?
如果JavaScript中不存在異步,只能自上而下執(zhí)行,如果上一行解析時間很長,那么下面的代碼就會被阻塞酝陈,不向下執(zhí)行毁涉。
頁面出來,用戶看到覺得是“卡死了”穆壕,所以需要異步其屏。
JavaScript單線程又是如何實現(xiàn)異步的呢?
是通過的事件循環(huán)(event loop)實現(xiàn)異步的。
JavaScript中的event loop()
JavaScript的執(zhí)行機制是
- 首先判斷JavaScript是同步還是異步,同步就進入主線程,異步就進入
event table
- 異步任務在
event table
中注冊函數(shù),當滿足觸發(fā)條件后,被推入event queue
- 同步任務進入主線程后一直執(zhí)行,直到主線程空閑時,才會去
event queue
中查看是否有可執(zhí)行的異步任務,如果有就推入主線程中
了解了這幾個概念川背,再來看看javascript是怎么執(zhí)行代碼的就比較輕松愉快了。開始吧
console.log(1)
Promise.resolve().then(function () {
console.log(2)
})
new Promise(function(resolve, reject){
console.log(3)
resolve()
}).then(function () {
console.log(4)
setTimeout(function () {
console.log(5)
})
})
console.log(6)
setTimeout(function () {
Promise.resolve().then(function () {
console.log(7)
setTimeout(function () {
console.log(8)
})
})
})
第一波先執(zhí)行宏任務
- JavaScript引擎在執(zhí)行這段代碼的時候膨更,首先將全局執(zhí)行上下文壓入棧中,然后呢荚守,在執(zhí)行的時候會碰到
console.log
函數(shù)练般,將它壓入棧中,這個時候,直接執(zhí)行console
函數(shù)敞贡,并輸出1
摄职。然后console
函數(shù)出棧 - 繼續(xù)往下執(zhí)行,碰到了
Promise.resolve().then()
,先將Promise.resolve().then()
壓入棧中,然后執(zhí)行Promise.resolve().then()
,前面說過,這個then()
函數(shù)是個微任務蛛倦,它會將傳入給它的回調(diào)函數(shù)加入到微任務隊列中。然后Promise.resolve().then()
就出棧了及皂。 - 接著執(zhí)行且改,遇到
promise
的構造函數(shù),這個構造函數(shù)是一個宏任務碍拆,會直接將傳遞給它的函數(shù)壓入棧中慨蓝。執(zhí)行console
函數(shù)并輸出3
,執(zhí)行完弧满,console
函數(shù)出棧此熬,接著執(zhí)行resolve()
函數(shù)滑进,并出棧扶关。然后繼續(xù)執(zhí)行then函數(shù)近哟,將傳遞給then函數(shù)的參數(shù)函數(shù)放到微任務隊列中: - 繼續(xù)來,繼續(xù)往下執(zhí)行疯淫。碰到
console.log(6)
,二話不說戳玫,直接壓入棧中,執(zhí)行币绩,輸出6
府阀,出棧,一氣呵成董瞻。 - 接著田巴,引擎碰到了
setTimeout
函數(shù),這家伙是個宏任務抄伍,但同時它會將傳遞給它的函數(shù)管宵,加入到任務隊列中:
好了谍珊,到此第一波宏任務就全部執(zhí)行完畢虫给。接著素跺,引擎就會去看一下微任務隊列中有沒有任務坡锡,如果有的話燕雁,執(zhí)行它們鲸拥。
第二波先執(zhí)行微任務
- 現(xiàn)在看到的是刑赶,微任務隊列中有兩個任務懂衩。按照隊列的先入先出規(guī)則,先從
function () {console.log(2)}
開始執(zhí)行牵敷。先是函數(shù)入棧法希,然后執(zhí)行函數(shù),輸出2
毛肋,然后函數(shù)出棧屋剑。
接著執(zhí)行下面這段代碼:
console.log(4)
setTimeout(function () {
console.log(5)
})
先從 console.log(4)
開始,先將它入棧孕讳,然后執(zhí)行它肄鸽,輸出4
油啤,然后函數(shù)出棧。
接著執(zhí)行:
setTimeout(function () {
console.log(5)
})
上面說過setTimeout
是宏任務,加入到任務隊列中去逮诲。
繼續(xù)向下執(zhí)行
function(){
Promise.resolve().then(function () {
console.log(7)
setTimeout(function () {
console.log(8)
})
})
}
這里執(zhí)行這個函數(shù)的時候遇到一個微任務幽告,將這個微任務添加到微任務隊列,這一波又執(zhí)行完了,接著就回去檢查微任務隊列中有沒有待執(zhí)行的任務齐唆,一看還真有兩個小可愛等待執(zhí)行冻河,于是沒什么好說的茉帅,直接擰出去就執(zhí)行堪澎。
第三波
- 先是執(zhí)行
console.log(7)
味滞,然后輸出7
。接著執(zhí)行setTimeout
昨凡,將傳遞給他的任務添加到任務隊列中去 - 最后就剩這兩個函數(shù)了攒暇,按照隊列的先入后出一次執(zhí)行吧,輸出5和8就轧。
最后看一下打印最后的結果就是1,3,6,2,4,7,5,8
田度。你寫對了嗎?
4.總結
最后牢記兩點
- JavaScript是單線程語言镇饺。
- JavaScript的
Event Loop
是JS的執(zhí)行機制。