JavaScript單線程實(shí)現(xiàn)異步的詳細(xì)代碼示例
1.瀏覽器內(nèi)核:
在講JavaScript異步之前,先講一下JavaScript運(yùn)行環(huán)境,因?yàn)镴avaScript是否能實(shí)現(xiàn)異步是通過運(yùn)行環(huán)境機(jī)制決定的,我們經(jīng)常使用的環(huán)境就是瀏覽器環(huán)境了,所以我今天主要講一下在瀏覽器的渲染進(jìn)程(瀏覽器內(nèi)核)如何執(zhí)行異步的。
負(fù)責(zé)頁面的渲染,腳本的執(zhí)行和時(shí)間處理,每一個(gè)tab頁也都表示一個(gè)進(jìn)程
對(duì)于渲染進(jìn)程來說,它其實(shí)就是多線程的:
- GUI渲染線程:負(fù)責(zé)頁面渲染解析 HTML/CSS 生成 DOM 樹和 CSSOM 樹等
- JS引擎線程:負(fù)責(zé)解析和執(zhí)行JavaScript腳本程序,并且一個(gè)進(jìn)程只有一個(gè)js引擎線程,js引擎是一個(gè)單線程
- 事件觸發(fā)線程:用來控制事件循環(huán)(click,setTimeout,ajax等),當(dāng)事件滿足條件的時(shí)候,將事件放到j(luò)s引擎所在的執(zhí)行隊(duì)列中
- 定時(shí)觸發(fā)器線程:setInterval與setTimeout所在的線程, 定時(shí)任務(wù)并不是由JS引擎計(jì)時(shí)的,是由定時(shí)觸發(fā)線程來計(jì)時(shí)的,執(zhí)行完畢會(huì)通知事件觸發(fā)進(jìn)程
- 異步http請(qǐng)求線程:處理ajax請(qǐng)求,當(dāng)完成后,通過回調(diào)函數(shù)觸發(fā)事件觸發(fā)線程
GUI渲染線程和JS引擎線程互斥:
當(dāng)JS引擎線程執(zhí)行時(shí)GUI渲染線程會(huì)被掛起,GUI更新則會(huì)被保存在一個(gè)隊(duì)列中等待JS引擎線程空閑時(shí)立即被執(zhí)行,防止渲染結(jié)果不可預(yù)期。
2.單線程:
javascript是單線程的,說明在任何一個(gè)時(shí)間點(diǎn),JavaScript 的主線程(也就是它的調(diào)用棧)只能執(zhí)行一件任務(wù)。如果前一個(gè)任務(wù)沒執(zhí)行完,后一個(gè)任務(wù)就必須排隊(duì)等待。
如果是這樣就會(huì)導(dǎo)致異步阻塞,很多任務(wù)沒有辦法瞬時(shí)完成,比如:
- 網(wǎng)絡(luò)請(qǐng)求:向服務(wù)器請(qǐng)求數(shù)據(jù),有時(shí)可能需要幾秒
- 定時(shí)器:需要到指定時(shí)間才能執(zhí)行
- 用戶事件:等待用戶的交互,發(fā)生時(shí)間不確定
3.異步:
異步就是為了解決單線程的問題,可以實(shí)現(xiàn)異步代碼不阻塞,當(dāng)觸發(fā)到異步代碼的時(shí)候,把它放到一遍
瀏覽器內(nèi)核是如何實(shí)現(xiàn)異步呢?
JavaScript本身是單線程的,它實(shí)現(xiàn)異步來自于它所運(yùn)行的環(huán)境—通常是瀏覽器,這個(gè)環(huán)境提供了一套復(fù)雜的機(jī)制,我們可以把它里面的一系列線程和機(jī)制策略想象成一個(gè)分工明確的團(tuán)隊(duì)。這個(gè)團(tuán)隊(duì)由四個(gè)核心成員組成:
- JS 主線程 & 調(diào)用棧 :這是 JavaScript 唯一的核心員工。他很勤奮,一次只能專心做一件事,所有同步任務(wù)都在他這里排隊(duì)執(zhí)行。
- 宿主環(huán)境 API :這是 JavaScript 的“外包團(tuán)隊(duì)”。瀏覽器提供了很多線程,比如定時(shí)器模塊、網(wǎng)絡(luò)請(qǐng)求模塊等。這些不占用 JS 主線程的時(shí)間,可以在后臺(tái)里獨(dú)立工作。
- 任務(wù)隊(duì)列:這是一個(gè)待辦列表。當(dāng)宿主環(huán)境完成了某項(xiàng)異步任務(wù)后(比如定時(shí)器時(shí)間到了,或者網(wǎng)絡(luò)請(qǐng)求成功返回了),他們不會(huì)直接把結(jié)果交給 JS 主線程(因?yàn)橹骶€程可能正在忙),而是把相應(yīng)的回調(diào)函數(shù)放到這個(gè)待辦列表里排隊(duì)。
- 事件循環(huán):調(diào)用棧 -> 微任務(wù) -> 宏任務(wù) -> 渲染
任務(wù)隊(duì)列又分為兩種:
- 宏任務(wù)隊(duì)列:存放 `setTimeout`, `setInterval`, I/O 操作,UI 渲染等任務(wù)的回調(diào)。我們上面講的“任務(wù)隊(duì)列”主要指的就是它。
- 微任務(wù)隊(duì)列:存放 `Promise.then()`, `async/await` 等任務(wù)的回調(diào)。
微任務(wù)的優(yōu)先級(jí)高于宏任務(wù),當(dāng)微任務(wù)和宏任務(wù)一起執(zhí)行完畢的時(shí)候并且調(diào)用棧是空的時(shí)候,先執(zhí)行微任務(wù)再執(zhí)行宏任務(wù)
setTimeout(() => console.log('Timeout (宏任務(wù))'), 0);
Promise.resolve().then(() => console.log('Promise (微任務(wù))'));
console.log('Script (同步)');
// 輸出順序:
// Script (同步)
// Promise (微任務(wù))
// Timeout (宏任務(wù))4.回調(diào)地獄:
當(dāng)多個(gè)相互依賴的異步操作需要按順序執(zhí)行時(shí),開發(fā)者將后一個(gè)操作的邏輯寫在了前一個(gè)操作的回調(diào)函數(shù)中,導(dǎo)致函數(shù)調(diào)用層層嵌套,形成一種橫向擴(kuò)展、難以閱讀和維護(hù)的代碼結(jié)構(gòu)。
回調(diào)地獄會(huì)導(dǎo)致什么問題:
1. 可讀性極差: 代碼不是從上到下線性執(zhí)行,而是像剝洋蔥一樣,一層包著一層。人的大腦很難快速理清其中的邏輯順序和依賴關(guān)系。
2. 難以維護(hù): 如果需求變更,比如要在第二步和第三步之間增加一個(gè)新的異步操作,你需要小心翼翼地找到正確的位置,插入新的嵌套層,并調(diào)整大量的花括號(hào)和縮進(jìn)。這極易出錯(cuò)。
3. 錯(cuò)誤處理復(fù)雜: `try...catch` 無法跨越異步邊界捕獲回調(diào)函數(shù)中的錯(cuò)誤。你必須在每一個(gè)嵌套層級(jí)都單獨(dú)處理錯(cuò)誤(就像上面代碼中的 `err1`, `err2`, `err3`),這導(dǎo)致代碼非常冗長和重復(fù)。
4. 耦合度高: 每一層的邏輯都和上一層的回調(diào)緊緊地耦合在一起,很難將某一步的邏輯抽離出來進(jìn)行復(fù)用。
典型的回調(diào)地獄:
console.log('開始!');
setTimeout(() => {
console.log('1秒過去了'); // 第一個(gè)回調(diào)
// 為了保證順序,第二個(gè)setTimeout必須嵌套在第一個(gè)回調(diào)內(nèi)部
setTimeout(() => {
console.log('又2秒過去了'); // 第二個(gè)回調(diào)
// 第三個(gè)setTimeout必須嵌套在第二個(gè)回調(diào)內(nèi)部
setTimeout(() => {
console.log('全部完成!'); // 第三個(gè)回調(diào)
}, 3000); // 3秒
}, 2000); // 2秒
}, 1000); // 1秒解決辦法:
1.使用 `Promise` 鏈?zhǔn)秸{(diào)用:
function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
delay(1000)
.then(() => {
console.log('1秒過去了');
// 返回一個(gè)新的Promise,以便繼續(xù)鏈接.then
return delay(2000);
})
.then(() => {
console.log('又2秒過去了');
return delay(3000);
})
.then(() => {
console.log('全部完成!');
})
.catch(err => {
// 統(tǒng)一處理鏈條中任何環(huán)節(jié)可能出現(xiàn)的錯(cuò)誤
console.error('出錯(cuò)了:', err);
});2.使用async/await:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 必須在一個(gè) async 標(biāo)記的函數(shù)內(nèi)部使用 await
async function runTimerSequence() {
try {
console.log('開始!');
await delay(1000); // “等待”1秒,但不會(huì)阻塞主線程
console.log('1秒過去了');
await delay(2000); // “等待”2秒
console.log('又2秒過去了');
await delay(3000); // “等待”3秒
console.log('全部完成!');
} catch (err) {
console.error('出錯(cuò)了:', err);
}
}
// 執(zhí)行這個(gè)異步函數(shù)
runTimerSequence();總結(jié)
到此這篇關(guān)于JavaScript單線程實(shí)現(xiàn)異步的文章就介紹到這了,更多相關(guān)JS單線程實(shí)現(xiàn)異步內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中函數(shù)的四種調(diào)用方式總結(jié)
這篇文章主要為大家詳細(xì)介紹了JavaScript中函數(shù)的四種調(diào)用方式,文中的示例代碼講解詳細(xì),對(duì)我們深入掌握J(rèn)avaScript有一定的幫助,需要的可以參考下2023-10-10
JS將所有對(duì)象s的屬性復(fù)制給對(duì)象r(原生js+jquery)
這篇文章主要介紹了js中將所有對(duì)象s的屬性復(fù)制給對(duì)象r的方法,原生js+jquery分別實(shí)現(xiàn)2014-01-01
javascript使用正則實(shí)現(xiàn)去掉字符串前面的所有0
這篇文章主要介紹了javascript使用正則實(shí)現(xiàn)去掉字符串前面的所有0,需要的朋友可以參考下2018-07-07
Node的優(yōu)勢我就不再亂吹捧了,它讓javascript統(tǒng)一web的前后臺(tái)成為了可能。但是對(duì)于新手來說,server端的JS代碼可能不像client端的代碼那么好調(diào)試,直觀。client端JS代碼的調(diào)試基本上經(jīng)歷了一個(gè)從“肉眼--alert()--firebug(或者其它的developer tools)”的一個(gè)過程。而對(duì)于server端的調(diào)試,可能新手仍然停留在使用“肉眼--console()”的階段。其實(shí),Node經(jīng)過了這么多年(雖然才短短幾年)的發(fā)展,也有了很多不錯(cuò)的第三方的調(diào)試工具。包括Node內(nèi)建的調(diào)試工具debugger、node-inspector等。2014-05-05
頁面載入結(jié)束自動(dòng)調(diào)用js函數(shù)示例
當(dāng)頁面加載完成后自動(dòng)調(diào)用預(yù)先編好的js函數(shù),在某些特殊情況下還是比較實(shí)用的,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下2013-09-09
關(guān)于javascript解決閉包漏洞的一個(gè)問題詳解
閉包在JavaScript高級(jí)程序設(shè)計(jì)(第3版)中是這樣描述:閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù),下面這篇文章主要給大家介紹了關(guān)于javascript解決閉包漏洞的一個(gè)問題的相關(guān)資料,需要的朋友可以參考下2022-11-11
JS實(shí)現(xiàn)可直接顯示網(wǎng)頁代碼運(yùn)行效果的HTML代碼預(yù)覽功能實(shí)例
這篇文章主要介紹了JS實(shí)現(xiàn)可直接顯示網(wǎng)頁代碼運(yùn)行效果的HTML代碼預(yù)覽功能,通過獲取文本框內(nèi)容并在新窗口打印輸出來實(shí)現(xiàn)直接運(yùn)行html代碼的功能,簡單實(shí)用,需要的朋友可以參考下2015-08-08
利用prop-types第三方庫對(duì)組件的props中的變量進(jìn)行類型檢測
本篇文章主要介紹了利用prop-types第三方庫對(duì)組件的props中的變量進(jìn)行類型檢測的相關(guān)知識(shí),具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-05-05

