2024-03-11 深入理解JavaScript運(yùn)行時(shí)機(jī)制:事件循環(huán)與單線程如何處理耗時(shí)任務(wù)

問(wèn)題1

原生模塊還可以使用 promise 來(lái)簡(jiǎn)化代碼,搭配 ES2016(ES7)標(biāo)準(zhǔn)的async/await語(yǔ)法則效果更佳。如果橋接原生方法的最后兩個(gè)參數(shù)是RCTPromiseResolveBlock和RCTPromiseRejectBlock,則對(duì)應(yīng)的 JS 方法就會(huì)返回一個(gè) Promise 對(duì)象。

我們把上面的代碼用 promise 來(lái)代替回調(diào)進(jìn)行重構(gòu):

RCT_REMAP_METHOD(findEvents,
                 findEventsWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
  NSArray *events = ...
  if (events) {
    resolve(events);
  } else {
    NSError *error = ...
    reject(@"no_events", @"There were no events", error);
  }
}

現(xiàn)在 JavaScript 端的方法會(huì)返回一個(gè) Promise。這樣你就可以在一個(gè)聲明了async的異步函數(shù)內(nèi)使用await關(guān)鍵字來(lái)調(diào)用,并等待其結(jié)果返回。(雖然這樣寫(xiě)著看起來(lái)像同步操作,但實(shí)際仍然是異步的,并不會(huì)阻塞執(zhí)行來(lái)等待)。

async function updateEvents() {
  try {
    const events = await CalendarManager.findEvents();

    this.setState({ events });
  } catch (e) {
    console.error(e);
  }
}

updateEvents(); 

怎么樣理解這里說(shuō)的:這樣你就可以在一個(gè)聲明了async的異步函數(shù)內(nèi)使用await關(guān)鍵字來(lái)調(diào)用,并等待其結(jié)果返回。(雖然這樣寫(xiě)著看起來(lái)像同步操作,但實(shí)際仍然是異步的,并不會(huì)阻塞執(zhí)行來(lái)等待)。

回答
這段說(shuō)明是在講述使用 Promise 和 async/await 來(lái)處理異步操作時(shí)的一種編碼風(fēng)格和思想。

首先,異步操作的處理方式有兩種:回調(diào)函數(shù)和 Promise?;卣{(diào)函數(shù)是傳統(tǒng)的方式,但它容易導(dǎo)致回調(diào)地獄(callback hell),代碼不易讀,不易維護(hù)。Promise 則是一種更為現(xiàn)代化的處理方式,它可以鏈?zhǔn)秸{(diào)用,更易于理解和組織。

而 async/await 則是在 Promise 的基礎(chǔ)上進(jìn)一步封裝的語(yǔ)法糖,使得異步代碼看起來(lái)更像同步代碼,更易讀。使用 async/await,你可以在異步函數(shù)內(nèi)部使用 await 關(guān)鍵字來(lái)等待 Promise 的結(jié)果,而不需要使用 .then() 方法處理回調(diào)。這樣的代碼看起來(lái)更加清晰和簡(jiǎn)潔。

在你提供的代碼示例中,通過(guò)將原生模塊的回調(diào)方法改為 Promise 形式,可以在 JavaScript 端的異步函數(shù)內(nèi)部使用 await 關(guān)鍵字來(lái)等待原生方法的結(jié)果,這樣做看起來(lái)就像同步操作一樣,但實(shí)際上并不會(huì)阻塞執(zhí)行。這是因?yàn)槭褂昧?async/await 語(yǔ)法,使得 JavaScript 運(yùn)行時(shí)可以在等待異步操作的同時(shí)繼續(xù)執(zhí)行其他任務(wù),直到異步操作完成并返回結(jié)果。

總之,這段說(shuō)明是在鼓勵(lì)使用 Promise 和 async/await 來(lái)處理異步操作,以提高代碼的可讀性和可維護(hù)性。

問(wèn)題2

User
當(dāng)程序執(zhí)行到 const events = await CalendarManager.findEvents();這一行的時(shí)候,會(huì)阻塞嗎。如果外面調(diào)用updateEvents方法也用了await

回答
當(dāng)程序執(zhí)行到 const events = await CalendarManager.findEvents(); 這一行時(shí),會(huì)發(fā)生暫停,但不會(huì)阻塞整個(gè)程序。JavaScript 運(yùn)行時(shí)會(huì)暫停執(zhí)行 updateEvents() 函數(shù),并等待 CalendarManager.findEvents() 方法的 Promise 對(duì)象被解決(即異步操作完成)。

如果外部調(diào)用 updateEvents() 方法也使用了 await 來(lái)等待其執(zhí)行結(jié)果,那么當(dāng)外部調(diào)用的代碼執(zhí)行到這個(gè) await updateEvents(); 時(shí),也會(huì)發(fā)生暫停,直到 updateEvents() 函數(shù)的執(zhí)行完畢并返回結(jié)果。

這種行為是 async/await 語(yǔ)法的特點(diǎn)之一,它允許在異步代碼中使用類(lèi)似同步的語(yǔ)法結(jié)構(gòu)來(lái)編寫(xiě)程序,但實(shí)際上并不會(huì)阻塞整個(gè)程序的執(zhí)行。JavaScript 運(yùn)行時(shí)會(huì)在異步操作等待的同時(shí)繼續(xù)執(zhí)行其他任務(wù),直到異步操作完成并返回結(jié)果,然后再繼續(xù)執(zhí)行后續(xù)的代碼。

問(wèn)題3

User
如何理解“暫停” 和“阻塞”

回答
理解“暫?!焙汀白枞钡母拍顚?duì)于理解異步編程非常重要。

暫停(Pausing):在異步操作中,當(dāng)程序執(zhí)行到一個(gè)異步操作(比如一個(gè) await 表達(dá)式)時(shí),該操作可能需要等待某些資源(比如網(wǎng)絡(luò)請(qǐng)求、文件讀取等)完成,然后才能繼續(xù)執(zhí)行后續(xù)的代碼。在這種情況下,當(dāng)前函數(shù)的執(zhí)行會(huì)暫停,JavaScript 引擎會(huì)轉(zhuǎn)而執(zhí)行其他可執(zhí)行的任務(wù)。當(dāng)異步操作完成后,JavaScript 引擎會(huì)回到原來(lái)的函數(shù),并繼續(xù)執(zhí)行后續(xù)的代碼。
阻塞(Blocking):阻塞是指當(dāng)一個(gè)任務(wù)執(zhí)行時(shí),它會(huì)阻止其他任務(wù)的執(zhí)行。在同步代碼中,如果一個(gè)任務(wù)需要等待某些操作完成,那么它會(huì)一直等待直到操作完成,期間不會(huì)執(zhí)行其他任務(wù)。這樣的行為被稱(chēng)為阻塞,因?yàn)樗柚沽顺绦虻钠渌糠掷^續(xù)執(zhí)行。
在 JavaScript 中,由于其單線程的特性,通常情況下是不會(huì)發(fā)生真正的阻塞的,因?yàn)?JavaScript 運(yùn)行時(shí)會(huì)采用事件循環(huán)機(jī)制來(lái)處理異步任務(wù),使得在等待異步操作完成的同時(shí),可以執(zhí)行其他任務(wù),從而避免了阻塞整個(gè)程序的執(zhí)行。相比之下,“暫?!备m用于描述異步操作等待的情況,而“阻塞”更適用于描述同步操作的行為。

問(wèn)題4

User
運(yùn)行時(shí)會(huì)采用事件循環(huán)機(jī)制來(lái)處理異步任務(wù),使得在等待異步操作完成的同時(shí),可以執(zhí)行其他任務(wù),從而避免了阻塞整個(gè)程序的執(zhí)行??梢耘e一個(gè)例子說(shuō)明嗎,在等待異步操作完成的同時(shí),在等待異步操作完成的同時(shí)

回答
當(dāng)在 JavaScript 中執(zhí)行異步操作時(shí),事件循環(huán)機(jī)制確保其他任務(wù)可以被執(zhí)行,而不會(huì)阻塞程序的執(zhí)行。這意味著在等待異步操作完成的同時(shí),JavaScript 運(yùn)行時(shí)可以處理其他任務(wù),比如響應(yīng)用戶(hù)輸入、執(zhí)行其他函數(shù)等。

舉個(gè)簡(jiǎn)單的例子,假設(shè)我們有一個(gè)異步函數(shù) fetchData() 用于從服務(wù)器獲取數(shù)據(jù),同時(shí)我們也想要在獲取數(shù)據(jù)的過(guò)程中更新界面以提示用戶(hù)正在加載數(shù)據(jù)。在這種情況下,我們可以使用事件循環(huán)機(jī)制來(lái)實(shí)現(xiàn):

javascript
Copy code

async function fetchData() {
    // 模擬一個(gè)異步操作,比如向服務(wù)器請(qǐng)求數(shù)據(jù)
    await new Promise(resolve => setTimeout(resolve, 2000)); // 模擬等待2秒鐘

    return "Data from server";
}

async function updateUI() {
    console.log("Updating UI...");
    // 更新界面,顯示加載數(shù)據(jù)的提示
    document.getElementById("loading-message").innerText = "Loading data...";

    // 在等待異步操作完成的同時(shí),JavaScript 運(yùn)行時(shí)可以處理其他任務(wù)
    const data = await fetchData();

    // 當(dāng)異步操作完成后,更新界面,顯示獲取到的數(shù)據(jù)
    console.log("Data received:", data);
    document.getElementById("loading-message").innerText = ""; // 清除加載數(shù)據(jù)的提示
    document.getElementById("data").innerText = data;
}

// 調(diào)用 updateUI 函數(shù)
updateUI();

console.log("Other tasks..."); // 這行代碼會(huì)立即執(zhí)行,不會(huì)等待異步操作

在這個(gè)例子中,當(dāng)調(diào)用 updateUI() 函數(shù)時(shí),它會(huì)立即開(kāi)始執(zhí)行。在 updateUI() 函數(shù)中,首先會(huì)更新界面以顯示加載數(shù)據(jù)的提示,然后調(diào)用 fetchData() 函數(shù)來(lái)獲取數(shù)據(jù)。雖然 fetchData() 函數(shù)是異步的,并且需要等待2秒鐘才能完成,但是在等待的過(guò)程中,JavaScript 運(yùn)行時(shí)仍然可以執(zhí)行其他任務(wù),比如輸出 "Other tasks...",這是因?yàn)槭录h(huán)機(jī)制的作用。當(dāng)數(shù)據(jù)獲取完成后,再更新界面以顯示獲取到的數(shù)據(jù)。

問(wèn)題5:

User
console.log("Other tasks..."); 這段代碼執(zhí)行的所在線程 和return "Data from server"; 這行代碼執(zhí)行的縣城 已經(jīng)等待2秒鐘的這個(gè)線程,是相同的線程嗎

回答
在 JavaScript 中,單線程是指所有 JavaScript 代碼都在同一個(gè)線程中執(zhí)行。這個(gè)線程被稱(chēng)為主線程或者稱(chēng)為 UI 線程,它負(fù)責(zé)執(zhí)行 JavaScript 代碼、處理用戶(hù)交互、渲染界面等任務(wù)。

在你提供的例子中,console.log("Other tasks..."); 這段代碼和 return "Data from server"; 這行代碼都是在主線程中執(zhí)行的,因?yàn)?JavaScript 代碼都運(yùn)行在同一個(gè)線程中。在 fetchData() 函數(shù)中,await new Promise(resolve => setTimeout(resolve, 2000)); 這行代碼是在主線程中執(zhí)行的,它創(chuàng)建了一個(gè)定時(shí)器,并在2秒鐘后將異步操作(Promise)的狀態(tài)設(shè)置為已完成。

盡管定時(shí)器的回調(diào)函數(shù)會(huì)在2秒鐘后執(zhí)行,但這并不會(huì)阻塞主線程的執(zhí)行,因?yàn)?JavaScript 運(yùn)行時(shí)會(huì)繼續(xù)處理其他任務(wù)。當(dāng)定時(shí)器到期后,它的回調(diào)函數(shù)會(huì)被添加到事件隊(duì)列中,等待主線程處理。一旦主線程空閑,事件循環(huán)會(huì)檢查事件隊(duì)列,將定時(shí)器的回調(diào)函數(shù)推入調(diào)用棧中執(zhí)行,從而完成異步操作。

所以,在等待2秒鐘的過(guò)程中,JavaScript 主線程會(huì)繼續(xù)執(zhí)行其他任務(wù),比如執(zhí)行 console.log("Other tasks...");。這兩行代碼都是在同一個(gè)線程中執(zhí)行的,即 JavaScript 主線程。

問(wèn)題6

User
這里提到了一個(gè)事件隊(duì)列的概念, 等待2秒這個(gè)底層是怎么實(shí)現(xiàn)的,是誰(shuí)一直在監(jiān)聽(tīng)這個(gè)倒計(jì)時(shí)

回答
在 JavaScript 運(yùn)行時(shí)中,事件隊(duì)列是用來(lái)管理異步操作的。當(dāng)一個(gè)異步操作完成時(shí),它會(huì)將其對(duì)應(yīng)的回調(diào)函數(shù)添加到事件隊(duì)列中,等待 JavaScript 主線程處理。

具體來(lái)說(shuō),當(dāng)調(diào)用 setTimeout 函數(shù)時(shí),JavaScript 運(yùn)行時(shí)會(huì)創(chuàng)建一個(gè)定時(shí)器,并將定時(shí)器的回調(diào)函數(shù)添加到事件隊(duì)列中。同時(shí),定時(shí)器會(huì)開(kāi)始計(jì)時(shí),當(dāng)計(jì)時(shí)器時(shí)間到期后,定時(shí)器的回調(diào)函數(shù)會(huì)被添加到事件隊(duì)列中。JavaScript 主線程會(huì)不斷地從事件隊(duì)列中取出事件,并執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù),這就是事件循環(huán)機(jī)制的核心。

在等待2秒鐘的過(guò)程中,底層的實(shí)現(xiàn)依賴(lài)于操作系統(tǒng)提供的定時(shí)器機(jī)制。在瀏覽器中,通常是使用瀏覽器提供的定時(shí)器 API,比如 setTimeout 和 setInterval。瀏覽器會(huì)使用底層的系統(tǒng) API 來(lái)管理定時(shí)器,比如 setTimeout 可能會(huì)調(diào)用操作系統(tǒng)提供的定時(shí)器功能,比如 setTimeout 的實(shí)現(xiàn)可能會(huì)調(diào)用 setTimeout 函數(shù)并注冊(cè)一個(gè)回調(diào)函數(shù),告訴操作系統(tǒng)在一定時(shí)間后執(zhí)行這個(gè)回調(diào)函數(shù)。操作系統(tǒng)會(huì)負(fù)責(zé)在指定時(shí)間到達(dá)后通知瀏覽器,然后瀏覽器會(huì)將定時(shí)器的回調(diào)函數(shù)添加到事件隊(duì)列中,等待 JavaScript 主線程處理。

總的來(lái)說(shuō),底層的實(shí)現(xiàn)是通過(guò)操作系統(tǒng)提供的定時(shí)器機(jī)制來(lái)實(shí)現(xiàn)的,而 JavaScript 運(yùn)行時(shí)會(huì)監(jiān)聽(tīng)這些定時(shí)器,并在適當(dāng)?shù)臅r(shí)候?qū)⒍〞r(shí)器的回調(diào)函數(shù)添加到事件隊(duì)列中。

問(wèn)題7

User
javascript運(yùn)行時(shí)機(jī)制是什么樣的

回答
JavaScript 運(yùn)行時(shí)的機(jī)制可以被描述為事件驅(qū)動(dòng)和單線程的。這個(gè)機(jī)制主要包括以下幾個(gè)組成部分:

事件循環(huán)(Event Loop):JavaScript 運(yùn)行時(shí)使用事件循環(huán)來(lái)處理異步操作。事件循環(huán)負(fù)責(zé)監(jiān)聽(tīng)事件隊(duì)列,當(dāng)事件隊(duì)列中有事件時(shí),事件循環(huán)會(huì)將事件取出并調(diào)用相應(yīng)的回調(diào)函數(shù)。事件循環(huán)會(huì)持續(xù)運(yùn)行,不斷地監(jiān)聽(tīng)事件隊(duì)列,執(zhí)行對(duì)應(yīng)的任務(wù)。
任務(wù)隊(duì)列(Task Queue):任務(wù)隊(duì)列存儲(chǔ)著待處理的任務(wù)或事件。在 JavaScript 中,任務(wù)隊(duì)列通常包括宏任務(wù)隊(duì)列(Macrotask Queue)和微任務(wù)隊(duì)列(Microtask Queue)。宏任務(wù)隊(duì)列用于存儲(chǔ)一些較大的任務(wù),比如 setTimeout、setInterval、I/O 操作等。微任務(wù)隊(duì)列用于存儲(chǔ)一些較小的任務(wù),比如 Promise 的回調(diào)函數(shù)、MutationObserver 的回調(diào)等。事件循環(huán)會(huì)首先處理微任務(wù)隊(duì)列中的所有任務(wù),然后再處理宏任務(wù)隊(duì)列中的任務(wù)。
調(diào)用棧(Call Stack):調(diào)用棧用來(lái)存儲(chǔ)當(dāng)前執(zhí)行的上下文(函數(shù)調(diào)用棧)。當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),它的執(zhí)行上下文會(huì)被壓入調(diào)用棧中,當(dāng)函數(shù)執(zhí)行完成后,它的執(zhí)行上下文會(huì)被彈出調(diào)用棧。調(diào)用棧的特性決定了 JavaScript 是單線程的,即一次只能執(zhí)行一個(gè)任務(wù),一次只能有一個(gè)函數(shù)在調(diào)用棧中執(zhí)行。
Web API:Web API 是瀏覽器提供的一組 API,用于處理一些異步操作,比如定時(shí)器、網(wǎng)絡(luò)請(qǐng)求、DOM 事件等。Web API 可以將異步操作的回調(diào)函數(shù)添加到事件隊(duì)列中,等待 JavaScript 主線程處理。
綜合起來(lái),JavaScript 運(yùn)行時(shí)通過(guò)事件循環(huán)、任務(wù)隊(duì)列和調(diào)用棧等機(jī)制來(lái)實(shí)現(xiàn)異步編程。當(dāng)有異步操作時(shí),JavaScript 運(yùn)行時(shí)會(huì)將異步操作的回調(diào)函數(shù)添加到事件隊(duì)列中,等待 JavaScript 主線程處理。主線程在空閑時(shí)會(huì)不斷地從事件隊(duì)列中取出事件,并執(zhí)行相應(yīng)的回調(diào)函數(shù),從而完成異步操作。這種機(jī)制保證了 JavaScript 是單線程的,同時(shí)能夠處理異步任務(wù),使得 JavaScript 能夠處理復(fù)雜的并發(fā)操作。

問(wèn)題8

User
既然JavaScript 是單線程的,javascript運(yùn)行時(shí)的事件循環(huán),也只是處理耗時(shí)任務(wù)的回調(diào)函數(shù)和存儲(chǔ)彈出調(diào)用棧的相關(guān)操作,那么真正復(fù)雜耗時(shí)的任務(wù)是誰(shuí)在處理,哪個(gè)線程在處理

回答
在 JavaScript 中,雖然主線程是單線程的,但是瀏覽器中的一些底層操作,比如網(wǎng)絡(luò)請(qǐng)求、DOM 操作、定時(shí)器等,是由瀏覽器的其他線程來(lái)處理的,而不是 JavaScript 主線程。

具體來(lái)說(shuō),瀏覽器通常會(huì)有多個(gè)線程來(lái)處理不同的任務(wù),比如:

GUI 渲染線程:負(fù)責(zé)渲染頁(yè)面,處理 DOM 樹(shù)的構(gòu)建和繪制,以及處理用戶(hù)交互事件,比如點(diǎn)擊、滾動(dòng)等。這個(gè)線程與 JavaScript 主線程是分開(kāi)的。
網(wǎng)絡(luò)線程:負(fù)責(zé)處理網(wǎng)絡(luò)請(qǐng)求和響應(yīng),包括發(fā)送請(qǐng)求、接收響應(yīng)、解析響應(yīng)等操作。這個(gè)線程通常是由瀏覽器底層的網(wǎng)絡(luò)模塊來(lái)管理的,與 JavaScript 主線程是分開(kāi)的。
定時(shí)器線程:負(fù)責(zé)處理定時(shí)器的計(jì)時(shí)和觸發(fā),當(dāng)定時(shí)器到期時(shí),會(huì)將定時(shí)器的回調(diào)函數(shù)添加到事件隊(duì)列中,等待 JavaScript 主線程處理。
Web Worker 線程:Web Worker 是一種特殊的 JavaScript 線程,它可以在后臺(tái)運(yùn)行,獨(dú)立于主線程,用于處理一些耗時(shí)的計(jì)算任務(wù),比如大量的數(shù)據(jù)處理、復(fù)雜的算法等。Web Worker 與主線程之間通過(guò)消息傳遞來(lái)通信,可以在 Web Worker 中執(zhí)行一些復(fù)雜耗時(shí)的任務(wù),而不會(huì)阻塞主線程。
總的來(lái)說(shuō),在 JavaScript 運(yùn)行時(shí)中,雖然主線程是單線程的,但瀏覽器會(huì)利用多線程來(lái)處理一些復(fù)雜的耗時(shí)任務(wù),從而提高頁(yè)面的響應(yīng)速度和用戶(hù)體驗(yàn)。JavaScript 主線程通過(guò)事件循環(huán)和任務(wù)隊(duì)列機(jī)制來(lái)管理異步操作,而其他線程則負(fù)責(zé)處理一些底層的操作,比如網(wǎng)絡(luò)請(qǐng)求、DOM 渲染等。

最后編輯于
?著作權(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ù)。

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