JavaScript運(yùn)行機(jī)制的理解

0.前言

又是新的一年啊!


timg.jpeg

1.簡(jiǎn)介

JavaScript 是腳本語(yǔ)言,JS也是單線程的,因此同一時(shí)間只能做一件事,也就是說(shuō)它一次僅能處理一個(gè)任務(wù)。

思考題
  • JavaScript為什么是單線程的? 為什么需要異步? 單線程又是如何實(shí)現(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í)行上下文簡(jiǎn)單來(lái)說(shuō)就是一個(gè)執(zhí)行環(huán)境。它有全局環(huán)境、函數(shù)環(huán)境和eval函數(shù)環(huán)境之分。它會(huì)在javascript引擎執(zhí)行你的腳本的時(shí)候去創(chuàng)建。

執(zhí)行棧(Execution Stack)

執(zhí)行棧也就是常說(shuō)的調(diào)用棧,它是一種擁有LIFO(后進(jìn)先出)的數(shù)據(jù)結(jié)構(gòu)。它會(huì)存儲(chǔ)代碼運(yùn)行時(shí)創(chuàng)建的執(zhí)行上下文

微任務(wù)(micro task)與宏任務(wù)(macro task)

javasript中的任務(wù)分為微任務(wù)和宏任務(wù)兩種,這兩種任務(wù)的執(zhí)行時(shí)機(jī)是不同的,因此區(qū)分js中哪些是宏任務(wù),哪些是微任務(wù)則十分重要。我們常見(jiàn)的宏任務(wù)有:script任務(wù)、setTimeout、ajax等,常見(jiàn)的微任務(wù)比較典型的是:Promise.resolve().then()、process.nextTick、MutationObserver等。

事件循環(huán)(event loop)

javasript是單線程的,一次僅能處理一個(gè)任務(wù)。但js所在的宿主環(huán)境,也就是我們所說(shuō)的瀏覽器并不是單線程的(這里宿主環(huán)境僅討論瀏覽器)。它在遇到一些任務(wù)時(shí),比如說(shuō)setTimeout、event listener等。它會(huì)告訴瀏覽器:老兄幫個(gè)忙,事成后通知我一聲,小弟我先干別的事去了。瀏覽器會(huì)回應(yīng)說(shuō):交給我吧,小老弟,事成后我放到任務(wù)隊(duì)列,自己去取啊。于是,javasript開(kāi)始執(zhí)行script任務(wù),執(zhí)行完了就開(kāi)始檢查有沒(méi)有微任務(wù)啊,沒(méi)有的話就從任務(wù)隊(duì)列開(kāi)始取宏任務(wù)執(zhí)行,每執(zhí)行完一次宏任務(wù),就去看看有沒(méi)有微任務(wù),有的話就執(zhí)行完成,再執(zhí)行宏任務(wù),如此往復(fù)。如下圖:

1.png

而準(zhǔn)確的劃分方式是:

  • macro-task(宏任務(wù)):包括整體代碼scriptsetTimeout,setInterval
  • micro-task(微任務(wù)):Promise,process.nextTick

3.回答思考題

JavaScript為什么是單線程的?

因?yàn)楝F(xiàn)在如果有兩個(gè)任務(wù)一個(gè)是刪除DOM節(jié)點(diǎn),一個(gè)是增加DOM節(jié)點(diǎn),瀏覽器該如何執(zhí)行?所以JavaScript是單線程

為什么需要異步?

如果JavaScript中不存在異步,只能自上而下執(zhí)行,如果上一行解析時(shí)間很長(zhǎng),那么下面的代碼就會(huì)被阻塞,不向下執(zhí)行。
頁(yè)面出來(lái),用戶看到覺(jué)得是“卡死了”,所以需要異步。

JavaScript單線程又是如何實(shí)現(xiàn)異步的呢?

是通過(guò)的事件循環(huán)(event loop)實(shí)現(xiàn)異步的。

JavaScript中的event loop()

JavaScript的執(zhí)行機(jī)制是

  • 首先判斷JavaScript是同步還是異步,同步就進(jìn)入主線程,異步就進(jìn)入event table
  • 異步任務(wù)在event table中注冊(cè)函數(shù),當(dāng)滿足觸發(fā)條件后,被推入event queue
  • 同步任務(wù)進(jìn)入主線程后一直執(zhí)行,直到主線程空閑時(shí),才會(huì)去event queue中查看是否有可執(zhí)行的異步任務(wù),如果有就推入主線程中

了解了這幾個(gè)概念,再來(lái)看看javascript是怎么執(zhí)行代碼的就比較輕松愉快了。開(kāi)始吧

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í)行宏任務(wù)

  • JavaScript引擎在執(zhí)行這段代碼的時(shí)候,首先將全局執(zhí)行上下文壓入棧中,然后呢,在執(zhí)行的時(shí)候會(huì)碰到console.log函數(shù),將它壓入棧中,這個(gè)時(shí)候,直接執(zhí)行console函數(shù),并輸出1。然后console函數(shù)出棧
  • 繼續(xù)往下執(zhí)行,碰到了Promise.resolve().then(),先將Promise.resolve().then()壓入棧中,然后執(zhí)行Promise.resolve().then(),前面說(shuō)過(guò),這個(gè)then()函數(shù)是個(gè)微任務(wù),它會(huì)將傳入給它的回調(diào)函數(shù)加入到微任務(wù)隊(duì)列中。然后Promise.resolve().then()就出棧了。
  • 接著執(zhí)行,遇到promise的構(gòu)造函數(shù),這個(gè)構(gòu)造函數(shù)是一個(gè)宏任務(wù),會(huì)直接將傳遞給它的函數(shù)壓入棧中。執(zhí)行console函數(shù)并輸出3,執(zhí)行完,console函數(shù)出棧,接著執(zhí)行resolve()函數(shù),并出棧。然后繼續(xù)執(zhí)行then函數(shù),將傳遞給then函數(shù)的參數(shù)函數(shù)放到微任務(wù)隊(duì)列中:
  • 繼續(xù)來(lái),繼續(xù)往下執(zhí)行。碰到console.log(6),二話不說(shuō),直接壓入棧中,執(zhí)行,輸出6,出棧,一氣呵成。
  • 接著,引擎碰到了setTimeout函數(shù),這家伙是個(gè)宏任務(wù),但同時(shí)它會(huì)將傳遞給它的函數(shù),加入到任務(wù)隊(duì)列中:

好了,到此第一波宏任務(wù)就全部執(zhí)行完畢。接著,引擎就會(huì)去看一下微任務(wù)隊(duì)列中有沒(méi)有任務(wù),如果有的話,執(zhí)行它們。

第二波先執(zhí)行微任務(wù)

  • 現(xiàn)在看到的是,微任務(wù)隊(duì)列中有兩個(gè)任務(wù)。按照隊(duì)列的先入先出規(guī)則,先從function () {console.log(2)}開(kāi)始執(zhí)行。先是函數(shù)入棧,然后執(zhí)行函數(shù),輸出2,然后函數(shù)出棧。
    接著執(zhí)行下面這段代碼:
console.log(4)
setTimeout(function () {
   console.log(5)
})

先從 console.log(4)開(kāi)始,先將它入棧,然后執(zhí)行它,輸出4,然后函數(shù)出棧。
接著執(zhí)行:

setTimeout(function () {
   console.log(5)
})

上面說(shuō)過(guò)setTimeout是宏任務(wù),加入到任務(wù)隊(duì)列中去。
繼續(xù)向下執(zhí)行

function(){
Promise.resolve().then(function () {
        console.log(7)
        setTimeout(function () {
            console.log(8)
        })
    })
}

這里執(zhí)行這個(gè)函數(shù)的時(shí)候遇到一個(gè)微任務(wù),將這個(gè)微任務(wù)添加到微任務(wù)隊(duì)列,這一波又執(zhí)行完了,接著就回去檢查微任務(wù)隊(duì)列中有沒(méi)有待執(zhí)行的任務(wù),一看還真有兩個(gè)小可愛(ài)等待執(zhí)行,于是沒(méi)什么好說(shuō)的,直接擰出去就執(zhí)行。

第三波

  • 先是執(zhí)行console.log(7),然后輸出7。接著執(zhí)行setTimeout,將傳遞給他的任務(wù)添加到任務(wù)隊(duì)列中去
  • 最后就剩這兩個(gè)函數(shù)了,按照隊(duì)列的先入后出一次執(zhí)行吧,輸出5和8。

最后看一下打印最后的結(jié)果就是1,3,6,2,4,7,5,8。你寫(xiě)對(duì)了嗎?

4.總結(jié)
最后牢記兩點(diǎn)

  • JavaScript是單線程語(yǔ)言。
  • JavaScript的Event Loop是JS的執(zhí)行機(jī)制。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容