溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

JavaScript中執(zhí)行上下文和執(zhí)行機(jī)制的示例分析

發(fā)布時(shí)間:2022-03-30 12:19:56 來(lái)源:億速云 閱讀:136 作者:小新 欄目:web開(kāi)發(fā)

這篇文章主要介紹JavaScript中執(zhí)行上下文和執(zhí)行機(jī)制的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

線程和進(jìn)程

說(shuō)js中的執(zhí)行上下文和js執(zhí)行機(jī)制之前我們來(lái)說(shuō)說(shuō)線程和進(jìn)程

什么是線程

用官方的話術(shù)來(lái)說(shuō) 線程CPU調(diào)度的最小單位。

什么是進(jìn)程

用官方的話術(shù)來(lái)說(shuō) 進(jìn)程CPU資源分配的最小單位。

線程和進(jìn)程的關(guān)系

線程是建立在進(jìn)程的基礎(chǔ)上的一次程序運(yùn)行單位,通俗點(diǎn)解釋線程就是程序中的一個(gè)執(zhí)行流,一個(gè)進(jìn)程可以有一個(gè)或多個(gè)線程。

一個(gè)進(jìn)程中只有一個(gè)執(zhí)行流稱作單線程,即程序執(zhí)行時(shí),所走的程序路徑按照連續(xù)順序排下來(lái),前面的必須處理好,后面的才會(huì)執(zhí)行。

一個(gè)進(jìn)程中有多個(gè)執(zhí)行流稱作多線程,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程來(lái)執(zhí)行不同的任務(wù), 也就是說(shuō)允許單個(gè)程序創(chuàng)建多個(gè)并行執(zhí)行的線程來(lái)完成各自的任務(wù)。

下面筆者舉一個(gè)簡(jiǎn)單的例子,比如我們打開(kāi)qq音樂(lè)聽(tīng)歌,qq音樂(lè)就可以理解為一個(gè)進(jìn)程,在qq音樂(lè)中我們可以邊聽(tīng)歌邊下載這里就是多線程,聽(tīng)歌是一個(gè)線程,下載是一個(gè)線程。如果我們?cè)俅蜷_(kāi)vscode來(lái)寫(xiě)代碼這就是另外一個(gè)進(jìn)程了。

進(jìn)程之間相互獨(dú)立,但同一進(jìn)程下的各個(gè)線程間有些資源是共享的。

線程的生命周期

線程的生命周期會(huì)經(jīng)歷五個(gè)階段。

  • 新建狀態(tài): 使用 new 關(guān)鍵字和 Thread 類(lèi)或其子類(lèi)建立一個(gè)線程對(duì)象后,該線程對(duì)象就處于新建狀態(tài)。它保持這個(gè)狀態(tài)直到程序 start() 這個(gè)線程。

  • 就緒狀態(tài): 當(dāng)線程對(duì)象調(diào)用了 start() 方法之后,該線程就進(jìn)入就緒狀態(tài)。就緒狀態(tài)的線程處于就緒隊(duì)列中,只要獲得 CPU 的使用權(quán)就可以立即運(yùn)行。

  • 運(yùn)行狀態(tài): 如果就緒狀態(tài)的線程獲取 CPU 資源,就可以執(zhí)行 run(),此時(shí)線程便處于運(yùn)行狀態(tài)。處于運(yùn)行狀態(tài)的線程最為復(fù)雜,它可以變?yōu)樽枞麪顟B(tài)、就緒狀態(tài)和死亡狀態(tài)。

  • 阻塞狀態(tài): 如果一個(gè)線程執(zhí)行了 sleep(睡眠)、suspend(掛起)、wait(等待)等方法,失去所占用資源之后,該線程就從運(yùn)行狀態(tài)進(jìn)入阻塞狀態(tài)。在睡眠時(shí)間已到或獲得設(shè)備資源后可以重新進(jìn)入就緒狀態(tài)??梢苑譃槿N:

    • 等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行 wait() 方法,使線程進(jìn)入到等待阻塞狀態(tài)。

    • 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因?yàn)橥芥i被其他線程占用)。

    • 其他阻塞:通過(guò)調(diào)用線程的 sleep()join() 發(fā)出了 I/O 請(qǐng)求時(shí),線程就會(huì)進(jìn)入到阻塞狀態(tài)。當(dāng) sleep() 狀態(tài)超時(shí),join() 等待線程終止或超時(shí),或者 I/O 處理完畢,線程重新轉(zhuǎn)入就緒狀態(tài)。

  • 死亡狀態(tài): 一個(gè)運(yùn)行狀態(tài)的線程完成任務(wù)或者其他終止條件發(fā)生時(shí),該線程就切換到終止?fàn)顟B(tài)。

JavaScript中執(zhí)行上下文和執(zhí)行機(jī)制的示例分析

js是單線程還是多線程呢

JS是單線程。JS 作為瀏覽器腳本語(yǔ)言其主要用途是與用戶互動(dòng),以及操作DOM。這決定了它只能是單線程,否則會(huì)帶來(lái)很復(fù)雜的同步問(wèn)題。比如,假定JavaScript同時(shí)有兩個(gè)線程,一個(gè)線程在某個(gè)DOM節(jié)點(diǎn)上添加內(nèi)容,另一個(gè)線程刪除了這個(gè)節(jié)點(diǎn),這時(shí)瀏覽器應(yīng)該以哪個(gè)線程為準(zhǔn)?

執(zhí)行上下文和執(zhí)行棧

什么是執(zhí)行上下文

當(dāng) JS 引擎解析到可執(zhí)行代碼片段(通常是函數(shù)調(diào)用階段)的時(shí)候,就會(huì)先做一些執(zhí)行前的準(zhǔn)備工作,這個(gè)  “準(zhǔn)備工作” ,就叫做  "執(zhí)行上下文(execution context 簡(jiǎn)稱 EC)"  或者也可以叫做執(zhí)行環(huán)境。

執(zhí)行上下文分類(lèi)

javascript 中有三種執(zhí)行上下文類(lèi)型,分別是:

  • 全局執(zhí)行上下文 這是默認(rèn)或者說(shuō)是最基礎(chǔ)的執(zhí)行上下文,一個(gè)程序中只會(huì)存在一個(gè)全局上下文,它在整個(gè) javascript 腳本的生命周期內(nèi)都會(huì)存在于執(zhí)行堆棧的最底部不會(huì)被棧彈出銷(xiāo)毀。全局上下文會(huì)生成一個(gè)全局對(duì)象(以瀏覽器環(huán)境為例,這個(gè)全局對(duì)象是 window),并且將 this 值綁定到這個(gè)全局對(duì)象上。

  • 函數(shù)執(zhí)行上下文 每當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),都會(huì)創(chuàng)建一個(gè)新的函數(shù)執(zhí)行上下文(不管這個(gè)函數(shù)是不是被重復(fù)調(diào)用的)。

  • Eval 函數(shù)執(zhí)行上下文 執(zhí)行在 eval 函數(shù)內(nèi)部的代碼也會(huì)有它屬于自己的執(zhí)行上下文,但由于并不經(jīng)常使用 eval,所以在這里不做分析。

什么是執(zhí)行棧?

前面我們說(shuō)到js在運(yùn)行的時(shí)候會(huì)創(chuàng)建執(zhí)行上下文,但是執(zhí)行上下文是需要存儲(chǔ)的,那用什么來(lái)存儲(chǔ)呢?就需要用到棧數(shù)據(jù)結(jié)構(gòu)了。

棧是一種先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu)。

JavaScript中執(zhí)行上下文和執(zhí)行機(jī)制的示例分析

所以總結(jié)來(lái)說(shuō)用來(lái)存儲(chǔ)代碼運(yùn)行時(shí)創(chuàng)建的執(zhí)行上下文就是執(zhí)行棧。

js執(zhí)行流程

在執(zhí)行一段代碼時(shí),JS 引擎會(huì)首先創(chuàng)建一個(gè)執(zhí)行棧,用來(lái)存放執(zhí)行上下文。

然后 JS 引擎會(huì)創(chuàng)建一個(gè)全局執(zhí)行上下文,并 push 到執(zhí)行棧中, 這個(gè)過(guò)程 JS 引擎會(huì)為這段代碼中所有變量分配內(nèi)存并賦一個(gè)初始值(undefined),在創(chuàng)建完成后,JS 引擎會(huì)進(jìn)入執(zhí)行階段,這個(gè)過(guò)程 JS 引擎會(huì)逐行的執(zhí)行代碼,即為之前分配好內(nèi)存的變量逐個(gè)賦值(真實(shí)值)。

如果這段代碼中存在 function 的調(diào)用,那么 JS 引擎會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文,并 push 到執(zhí)行棧中,其創(chuàng)建和執(zhí)行過(guò)程跟全局執(zhí)行上下文一樣。

當(dāng)一個(gè)執(zhí)行棧執(zhí)行完畢后該執(zhí)行上下文就會(huì)從棧中彈出,接下來(lái)會(huì)進(jìn)入下一個(gè)執(zhí)行上下文。

下面筆者來(lái)舉個(gè)例子,假如在我們的程序中有如下代碼

console.log("Global Execution Context start");

function first() {
  console.log("first function");
  second();
  console.log("Again first function");
}

function second() {
  console.log("second function");
}

first();
console.log("Global Execution Context end");

上面的例子我們簡(jiǎn)單來(lái)分析下

  • 首先會(huì)創(chuàng)建一個(gè)執(zhí)行棧

  • 然后會(huì)創(chuàng)建一個(gè)全局上下文,并將該執(zhí)行上下文push到執(zhí)行棧中

  • 開(kāi)始執(zhí)行,輸出Global Execution Context start

  • 遇到first方法,執(zhí)行該方法,創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文并push到執(zhí)行棧

  • 執(zhí)行first執(zhí)行上下文,輸出first function

  • 遇到second方法,執(zhí)行該方法,創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文并push到執(zhí)行棧

  • 執(zhí)行second執(zhí)行上下文,輸出second function

  • second執(zhí)行上下文執(zhí)行完畢,從棧中彈出,進(jìn)入到下一個(gè)執(zhí)行上下文first執(zhí)行上下文

  • first執(zhí)行上下文繼續(xù)執(zhí)行,輸出Again first function

  • first執(zhí)行上下文執(zhí)行完畢,從棧中彈出,進(jìn)入到下一個(gè)執(zhí)行上下文全局執(zhí)行上下文

  • 全局執(zhí)行上下文繼續(xù)執(zhí)行,輸出Global Execution Context end

我們用一張圖來(lái)總結(jié)

JavaScript中執(zhí)行上下文和執(zhí)行機(jī)制的示例分析

好了。說(shuō)完執(zhí)行上下文和執(zhí)行棧我們?cè)賮?lái)說(shuō)說(shuō)js的執(zhí)行機(jī)制

執(zhí)行機(jī)制

說(shuō)到js的執(zhí)行機(jī)制,我們就需要了解js中同步任務(wù)和異步任務(wù)、宏任務(wù)和微任務(wù)了。

同步任務(wù)和異步任務(wù)

js中,任務(wù)分為同步任務(wù)和異步任務(wù),那什么是同步任務(wù)什么是異步任務(wù)呢?

同步任務(wù)指的是,在主線程上排隊(duì)執(zhí)行的任務(wù),只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù)。

異步任務(wù)指的是,不進(jìn)入主線程、而進(jìn)入"任務(wù)隊(duì)列"的任務(wù)(任務(wù)隊(duì)列中的任務(wù)與主線程并列執(zhí)行),只有當(dāng)主線程空閑了并且"任務(wù)隊(duì)列"通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。由于是隊(duì)列存儲(chǔ)所以滿足先進(jìn)先出規(guī)則。常見(jiàn)的異步任務(wù)有我們的setIntervalsetTimeout、promise.then等。

事件循環(huán)

前面介紹了同步任務(wù)和異步任務(wù),下面我們來(lái)說(shuō)說(shuō)事件循環(huán)。

  • 同步和異步任務(wù)分別進(jìn)入不同的執(zhí)行"場(chǎng)所",同步的進(jìn)入主線程,只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù)。異步任務(wù)不進(jìn)入主線程而是進(jìn)入 Event Table 并注冊(cè)函數(shù)。

  • 當(dāng)指定的事情完成時(shí),Event Table 會(huì)將這個(gè)函數(shù)移入 Event QueueEvent Queue是隊(duì)列數(shù)據(jù)結(jié)構(gòu),所以滿足先進(jìn)先出規(guī)則。

  • 主線程內(nèi)的任務(wù)執(zhí)行完畢為空,會(huì)去 Event Queue 讀取對(duì)應(yīng)的函數(shù),進(jìn)入主線程執(zhí)行。

上述過(guò)程會(huì)不斷重復(fù),也就是常說(shuō)的 Event Loop(事件循環(huán))

我們用一張圖來(lái)總結(jié)下

JavaScript中執(zhí)行上下文和執(zhí)行機(jī)制的示例分析

下面筆者簡(jiǎn)單來(lái)介紹個(gè)例子

function test1() {
  console.log("log1");

  setTimeout(() => {
    console.log("setTimeout 1000");
  }, 1000);

  setTimeout(() => {
    console.log("setTimeout 100");
  }, 100);

  console.log("log2");
}

test1(); // log1、log2、setTimeout 100、setTimeout 1000
  • 我們知道在js中會(huì)優(yōu)先執(zhí)行同步任務(wù)再執(zhí)行異步任務(wù),所以上面的例子會(huì)先輸出log1、log2

  • 同步任務(wù)執(zhí)行完后會(huì)執(zhí)行異步任務(wù),所以延遲100毫秒的回調(diào)函數(shù)會(huì)優(yōu)先執(zhí)行輸出setTimeout 100

  • 延遲1000毫秒的回調(diào)函數(shù)會(huì)后執(zhí)行輸出setTimeout 1000

上面的例子比較簡(jiǎn)單,相信只要你看懂了上面筆者說(shuō)的同步異步任務(wù)做出來(lái)是沒(méi)什么問(wèn)題的。那下面筆者再舉一個(gè)例子小伙伴們看看會(huì)輸出啥呢?

function test2() {
  console.log("log1");

  setTimeout(() => {
    console.log("setTimeout 1000");
  }, 1000);

  setTimeout(() => {
    console.log("setTimeout 100");
  }, 100);

  new Promise((resolve, reject) => {
    console.log("new promise");
    resolve();
  }).then(() => {
    console.log("promise.then");
  });

  console.log("log2");
}

test2();

要解決上面的問(wèn)題光知道同步和異步任務(wù)是不夠的,我們還得知道宏任務(wù)和微任務(wù)。

宏任務(wù)和微任務(wù)

js中,任務(wù)被分為兩種,一種叫宏任務(wù)MacroTask,一種叫微任務(wù)MicroTask。

常見(jiàn)的宏任務(wù)MacroTask

  • 主代碼塊

  • setTimeout()

  • setInterval()

  • setImmediate() - Node

  • requestAnimationFrame() - 瀏覽器

常見(jiàn)的微任務(wù)MicroTask

  • Promise.then()

  • process.nextTick() - Node

所以在上面的例子中就涉及到宏任務(wù)和微任務(wù)了,那宏任務(wù)微任務(wù)的執(zhí)行順序是怎么樣的呢?

  • 首先,整體的 script(作為第一個(gè)宏任務(wù))開(kāi)始執(zhí)行的時(shí)候,會(huì)把所有代碼分為同步任務(wù)、異步任務(wù)兩部分,同步任務(wù)會(huì)直接進(jìn)入主線程依次執(zhí)行,異步任務(wù)會(huì)進(jìn)入異步隊(duì)列然后再分為宏任務(wù)和微任務(wù)。

  • 宏任務(wù)進(jìn)入到 Event Table 中,并在里面注冊(cè)回調(diào)函數(shù),每當(dāng)指定的事件完成時(shí),Event Table 會(huì)將這個(gè)函數(shù)移到 Event Queue

  • 微任務(wù)也會(huì)進(jìn)入到另一個(gè) Event Table 中,并在里面注冊(cè)回調(diào)函數(shù),每當(dāng)指定的事件完成時(shí),Event Table 會(huì)將這個(gè)函數(shù)移到 Event Queue

  • 當(dāng)主線程內(nèi)的任務(wù)執(zhí)行完畢,主線程為空時(shí),會(huì)檢查微任務(wù)的 Event Queue,如果有任務(wù),就全部執(zhí)行,如果沒(méi)有就執(zhí)行下一個(gè)宏任務(wù)

我們用一張圖來(lái)總結(jié)下

JavaScript中執(zhí)行上下文和執(zhí)行機(jī)制的示例分析

讀懂了異步里面的宏任務(wù)和微任務(wù)上面的例子我們就可以輕易的得到答案了。

  • 我們知道在js中會(huì)優(yōu)先執(zhí)行同步任務(wù)再執(zhí)行異步任務(wù),所以上面的例子會(huì)先輸出log1、new promise、log2。這里需要注意new promise里面是同步的

  • 主代碼塊作為宏任務(wù)執(zhí)行完后會(huì)執(zhí)行此宏任務(wù)所產(chǎn)生的所有微任務(wù),所以會(huì)輸出promise.then

  • 所有微任務(wù)執(zhí)行完畢后會(huì)再執(zhí)行一個(gè)宏任務(wù),延遲100毫秒的回調(diào)函數(shù)會(huì)優(yōu)先執(zhí)行輸出setTimeout 100

  • 此宏任務(wù)沒(méi)有產(chǎn)生微任務(wù),所以沒(méi)有微任務(wù)需要執(zhí)行

  • 繼續(xù)執(zhí)行下一個(gè)宏任務(wù),延遲1000毫秒的回調(diào)函數(shù)會(huì)優(yōu)執(zhí)行輸出setTimeout 1000

所以test2方法執(zhí)行后會(huì)依次輸出log1、new promise、log2、promise.then、setTimeout 100、setTimeout 1000

關(guān)于js執(zhí)行到底是先宏任務(wù)再微任務(wù)還是先微任務(wù)再宏任務(wù)網(wǎng)上的文章各有說(shuō)辭。筆者的理解是如果把整個(gè)js代碼塊當(dāng)做宏任務(wù)的時(shí)候我們的js執(zhí)行順序是先宏任務(wù)后微任務(wù)的。

正所謂百看不如一練,下面筆者舉兩個(gè)例子如果你都能做對(duì)那你算是掌握了js執(zhí)行機(jī)制這一塊的知識(shí)了。

例子1

function test3() {
  console.log(1);

  setTimeout(function () {
    console.log(2);
    new Promise(function (resolve) {
      console.log(3);
      resolve();
    }).then(function () {
      console.log(4);
    });
    console.log(5);
  }, 1000);

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

  setTimeout(function () {
    console.log(9);
    new Promise(function (resolve) {
      console.log(10);
      resolve();
    }).then(function () {
      console.log(11);
    });
  }, 100);

  console.log(12);
}

test3();

我們來(lái)具體分析下

  • 首先js整體代碼塊作為一個(gè)宏任務(wù)最開(kāi)始執(zhí)行,依次輸出1、6、12

  • 整體代碼塊宏任務(wù)執(zhí)行完畢后產(chǎn)生了一個(gè)微任務(wù)和兩個(gè)宏任務(wù),所以宏任務(wù)隊(duì)列有兩個(gè)宏任務(wù),微任務(wù)隊(duì)列有一個(gè)微任務(wù)。

  • 宏任務(wù)執(zhí)行完畢后會(huì)執(zhí)行此宏任務(wù)所產(chǎn)生的的所有微任務(wù)。因?yàn)橹挥幸粋€(gè)微任務(wù),所以會(huì)輸出7。此微任務(wù)又產(chǎn)生了一個(gè)宏任務(wù),所以宏任務(wù)隊(duì)列目前有三個(gè)宏任務(wù)。

  • 三個(gè)宏任務(wù)里面沒(méi)有設(shè)置延遲的最先執(zhí)行,所以輸出8,此宏任務(wù)沒(méi)有產(chǎn)生微任務(wù),所以沒(méi)有微任務(wù)要執(zhí)行,繼續(xù)執(zhí)行下一個(gè)宏任務(wù)。

  • 延遲100毫秒的宏任務(wù)執(zhí)行,輸出9、10,并產(chǎn)生了一個(gè)微任務(wù),所以微任務(wù)隊(duì)列目前有一個(gè)微任務(wù)

  • 宏任務(wù)執(zhí)行完畢后會(huì)執(zhí)行該宏任務(wù)所產(chǎn)生的所有微任務(wù),所以會(huì)執(zhí)行微任務(wù)隊(duì)列的所有微任務(wù),輸出11

  • 延遲1000毫秒的宏任務(wù)執(zhí)行輸出2、3、5,并產(chǎn)生了一個(gè)微任務(wù),所以微任務(wù)隊(duì)列目前有一個(gè)微任務(wù)

  • 宏任務(wù)執(zhí)行完畢后會(huì)執(zhí)行該宏任務(wù)所產(chǎn)生的所有微任務(wù),所以會(huì)執(zhí)行微任務(wù)隊(duì)列的所有微任務(wù),輸出4

所以上面代碼例子會(huì)依次輸出1、6、12、7、8、9、10、11、2、3、5、4,小伙伴們是否做對(duì)了呢?

例子2

我們把上面的例子1稍作修改,引入asyncawait

async function test4() {
  console.log(1);

  setTimeout(function () {
    console.log(2);
    new Promise(function (resolve) {
      console.log(3);
      resolve();
    }).then(function () {
      console.log(4);
    });
    console.log(5);
  }, 1000);

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

  const result = await async1();
  console.log(result);

  setTimeout(function () {
    console.log(9);
    new Promise(function (resolve) {
      console.log(10);
      resolve();
    }).then(function () {
      console.log(11);
    });
  }, 100);

  console.log(12);
}

async function async1() {
  console.log(13)
  return Promise.resolve("Promise.resolve");
}

test4();

上面這里例子會(huì)輸出什么呢?這里我們弄懂asyncawait題目就迎刃而解了。

我們知道asyncawait其實(shí)是Promise的語(yǔ)法糖,這里我們只需要知道await后面就相當(dāng)于Promise.then。所以上面的例子我們可以理解成如下代碼

function test4() {
  console.log(1);

  setTimeout(function () {
    console.log(2);
    new Promise(function (resolve) {
      console.log(3);
      resolve();
    }).then(function () {
      console.log(4);
    });
    console.log(5);
  }, 1000);

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

  new Promise(function (resolve) {
    console.log(13);
    return resolve("Promise.resolve");
  }).then((result) => {
    console.log(result);

    setTimeout(function () {
      console.log(9);
      new Promise(function (resolve) {
        console.log(10);
        resolve();
      }).then(function () {
        console.log(11);
      });
    }, 100);

    console.log(12);
  });
}

test4();

看到上面的代碼是不是就能輕易得出結(jié)果呢?

  • 首先js整體代碼塊作為一個(gè)宏任務(wù)最開(kāi)始執(zhí)行,依次輸出1、6、13。

  • 整體代碼塊宏任務(wù)執(zhí)行完畢后產(chǎn)生了兩個(gè)微任務(wù)和一個(gè)宏任務(wù),所以宏任務(wù)隊(duì)列有一個(gè)宏任務(wù),微任務(wù)隊(duì)列有兩個(gè)微任務(wù)。

  • 宏任務(wù)執(zhí)行完畢后會(huì)執(zhí)行此宏任務(wù)所產(chǎn)生的的所有微任務(wù)。所以會(huì)輸出7、Promise.resolve、12。此微任務(wù)又產(chǎn)生了兩個(gè)宏任務(wù),所以宏任務(wù)隊(duì)列目前有三個(gè)宏任務(wù)。

  • 三個(gè)宏任務(wù)里面沒(méi)有設(shè)置延遲的最先執(zhí)行,所以輸出8,此宏任務(wù)沒(méi)有產(chǎn)生微任務(wù),所以沒(méi)有微任務(wù)要執(zhí)行,繼續(xù)執(zhí)行下一個(gè)宏任務(wù)。

  • 延遲100毫秒的宏任務(wù)執(zhí)行,輸出9、10,并產(chǎn)生了一個(gè)微任務(wù),所以微任務(wù)隊(duì)列目前有一個(gè)微任務(wù)

  • 宏任務(wù)執(zhí)行完畢后會(huì)執(zhí)行該宏任務(wù)所產(chǎn)生的所有微任務(wù),所以會(huì)執(zhí)行微任務(wù)隊(duì)列的所有微任務(wù),輸出11

  • 延遲1000毫秒的宏任務(wù)執(zhí)行輸出2、3、5,并產(chǎn)生了一個(gè)微任務(wù),所以微任務(wù)隊(duì)列目前有一個(gè)微任務(wù)

  • 宏任務(wù)執(zhí)行完畢后會(huì)執(zhí)行該宏任務(wù)所產(chǎn)生的所有微任務(wù),所以會(huì)執(zhí)行微任務(wù)隊(duì)列的所有微任務(wù),輸出4

所以上面代碼例子會(huì)依次輸出1、6、13、7、Promise.resolve、12、8、9、10、11、2、3、5、4,小伙伴們是否做對(duì)了呢?

擴(kuò)展

setTimeout(fn, 0)

關(guān)于setTimeout(fn)可能很多小伙伴還是不太理解,這不明明沒(méi)設(shè)置延遲時(shí)間嗎,不應(yīng)該立即就執(zhí)行嗎?

setTimeout(fn)我們可以理解成setTimeout(fn,0),其實(shí)是同一個(gè)意思。

我們知道js分同步任務(wù)和異步任務(wù),setTimeout(fn)就是屬于異步任務(wù),所以這里就算你沒(méi)設(shè)置延遲時(shí)間,他也會(huì)進(jìn)入異步隊(duì)列,需要等到主線程空閑的時(shí)候才會(huì)執(zhí)行。

筆者這里再提一嘴,你覺(jué)得我們?cè)?code>setTimeout后面設(shè)置的延遲時(shí)間,js就一定會(huì)按我們的延遲時(shí)間執(zhí)行嗎,我覺(jué)得并不見(jiàn)得。我們?cè)O(shè)置的時(shí)間只是該回調(diào)函數(shù)可以被執(zhí)行了,但是主線程有沒(méi)有空還是另外一回事,我們可以舉個(gè)簡(jiǎn)單的例子。

function test5() {
  setTimeout(function () {
    console.log("setTimeout");
  }, 100);

  let i = 0;
  while (true) {
    i++;
  }
}

test5();

上面的例子一定會(huì)在100毫秒后輸出setTimeout嗎,并不會(huì),因?yàn)槲覀兊闹骶€程進(jìn)入了死循環(huán),并沒(méi)有空去執(zhí)行異步隊(duì)列的任務(wù)。

GUI渲染

GUI渲染在這里說(shuō)有些小伙伴可能不太理解,后面筆者會(huì)出關(guān)于瀏覽器的文章會(huì)再詳細(xì)介紹,這里只是簡(jiǎn)單了解下即可。

由于JS引擎線程GUI渲染線程是互斥的關(guān)系,瀏覽器為了能夠使宏任務(wù)DOM任務(wù)有序的進(jìn)行,會(huì)在一個(gè)宏任務(wù)執(zhí)行結(jié)果后,在下一個(gè)宏任務(wù)執(zhí)行前,GUI渲染線程開(kāi)始工作,對(duì)頁(yè)面進(jìn)行渲染。

所以宏任務(wù)、微任務(wù)、GUI渲染之間的關(guān)系如下

宏任務(wù) -> 微任務(wù) -> GUI渲染 -> 宏任務(wù) -> ...

以上是“JavaScript中執(zhí)行上下文和執(zhí)行機(jī)制的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI