淺談JavaScript 單線程防阻塞的原理
JavaScript 是一種單線程語言,這意味著它一次只能執(zhí)行一個任務(wù)。這種設(shè)計(jì)可能會導(dǎo)致一些問題,比如當(dāng)遇到耗時的操作時,整個程序可能會被阻塞。為了解決這個問題,JavaScript 使用了事件循環(huán)和回調(diào)函數(shù)的機(jī)制,實(shí)現(xiàn)了非阻塞式的異步操作。
事件循環(huán)
JavaScript 有一個事件隊(duì)列,用于存放需要執(zhí)行的任務(wù)。事件循環(huán)不斷地檢查這個事件隊(duì)列,當(dāng)隊(duì)列中有可執(zhí)行的任務(wù)時,就從隊(duì)列中取出任務(wù)執(zhí)行。
當(dāng) JavaScript 遇到耗時的操作(如 I/O 操作)時,它會將這些操作交給瀏覽器或 Node.js 運(yùn)行時處理,并繼續(xù)執(zhí)行事件循環(huán)中的其他任務(wù)。這樣可以確保主線程不會被阻塞,保持程序的響應(yīng)性和交互性。
宏任務(wù)和微任務(wù)
事件隊(duì)列中的任務(wù)分為兩種類型:宏任務(wù)和微任務(wù)。
- 宏任務(wù)包括常見的 JavaScript 代碼、setTimeout、setInterval 等。這些任務(wù)會被添加到事件隊(duì)列的末尾,等待事件循環(huán)執(zhí)行。
- 微任務(wù)包括 Promise 的回調(diào)、process.nextTick (Node.js) 等。這些任務(wù)會被添加到當(dāng)前宏任務(wù)執(zhí)行完成后的微任務(wù)隊(duì)列中,優(yōu)先于下一個宏任務(wù)執(zhí)行。
事件循環(huán)在執(zhí)行一個宏任務(wù)后,會檢查并執(zhí)行所有的微任務(wù)隊(duì)列,然后再執(zhí)行下一個宏任務(wù)。這種機(jī)制確保了微任務(wù)可以更快地得到執(zhí)行,提高了程序的響應(yīng)性。
回調(diào)函數(shù)
當(dāng)耗時的操作完成時,瀏覽器或 Node.js 運(yùn)行時會觸發(fā)一個事件,并將相應(yīng)的回調(diào)函數(shù)放入事件隊(duì)列中。
事件循環(huán)會檢測到這個回調(diào)函數(shù),并從隊(duì)列中取出執(zhí)行它。這就實(shí)現(xiàn)了非阻塞式的異步操作,防止單線程的 JavaScript 被耗時的操作阻塞。
實(shí)際應(yīng)用
這種事件循環(huán)、宏任務(wù)和微任務(wù)的機(jī)制在 JavaScript 編程中廣泛應(yīng)用:
- 網(wǎng)頁交互: 在網(wǎng)頁開發(fā)中,我們經(jīng)常使用回調(diào)函數(shù)來處理用戶交互,如點(diǎn)擊事件、表單提交等。這些操作通常不會阻塞主線程,確保頁面保持高度響應(yīng)性。
- 網(wǎng)絡(luò)請求: 當(dāng)發(fā)送 AJAX 請求時,我們通常會使用回調(diào)函數(shù)來處理服務(wù)器的響應(yīng)。這樣可以確保在請求過程中,用戶仍然可以與頁面進(jìn)行交互。
- 定時器:
setTimeout()和setInterval()函數(shù)也利用了事件循環(huán)的機(jī)制。它們會將定時器任務(wù)添加到事件隊(duì)列中,等待執(zhí)行。 - Node.js 中的異步操作: Node.js 也是基于事件循環(huán)和回調(diào)函數(shù)的機(jī)制實(shí)現(xiàn)異步操作,如文件 I/O、數(shù)據(jù)庫查詢等。這確保了 Node.js 應(yīng)用程序可以高效地處理大量并發(fā)請求。
此外,還有一些其他的應(yīng)用場景:
- 事件驅(qū)動模型: JavaScript 的事件循環(huán)機(jī)制很適合實(shí)現(xiàn)事件驅(qū)動模型,這在 Web 開發(fā)和 Node.js 中廣泛應(yīng)用。
- 錯誤處理: 當(dāng)異步操作發(fā)生錯誤時,可以通過回調(diào)函數(shù)的方式進(jìn)行錯誤處理,避免整個程序崩潰。
- 流式處理: 在處理大量數(shù)據(jù)時,可以使用流的方式進(jìn)行異步處理,提高性能和內(nèi)存利用率。
javaScript 如何實(shí)現(xiàn)高并發(fā)與多線程?
雖然 JS 是單線程執(zhí)行模型,但通過瀏覽器或 Node.js 提供的機(jī)制,我們可以實(shí)現(xiàn)“偽并發(fā)”或“多線程模擬”,主要方式如下:
? 異步操作(等加載時先干別的)
- 原理:任務(wù)被掛起,等待資源時讓出主線程,通過事件隊(duì)列機(jī)制在任務(wù)完成后重新調(diào)度執(zhí)行。
- 常用方式:
setTimeout/setIntervalPromiseasync/await- Ajax / Fetch API
? Web Worker(開小號偷偷干活)
- 開啟一個獨(dú)立的線程運(yùn)行 JS 腳本,不影響主線程。
- 適用于大計(jì)算任務(wù)、離線數(shù)據(jù)預(yù)處理等。
- 與主線程通信使用
postMessage()/onmessage
// main.js
const worker = new Worker("worker.js")
worker.postMessage("開始計(jì)算")
worker.onmessage = (e) => {
console.log("子線程結(jié)果:", e.data)
}
// worker.js
onmessage = function (e) {
// 執(zhí)行密集任務(wù)
let sum = 0
for (let i = 0; i < 1e8; i++) sum += i
postMessage(sum)
}
? Node.js 中的 Worker Threads
- 使用
worker_threads模塊在后端實(shí)現(xiàn)多線程能力,適合 CPU 密集型場景。
? 任務(wù)拆碎(把大活切成小碎活穿插著做)
- 利用
requestIdleCallback、setTimeout分片處理數(shù)據(jù),減少卡頓。
異步與同步的區(qū)別
同步(Synchronous)
- 執(zhí)行順序嚴(yán)格,必須等待上一個任務(wù)完成后才能執(zhí)行下一個。
- 阻塞主線程。
console.log("A")
document.querySelector("button").click() // 阻塞直到點(diǎn)擊
console.log("B")
異步(Asynchronous)
- 后臺處理任務(wù),不阻塞主線程,通過回調(diào)或事件通知結(jié)果。
console.log("A")
setTimeout(() => console.log("B"), 1000)
console.log("C")
// 輸出順序:A -> C -> B
Promise、async 和 await 的理解與使用
Promise
- 用于封裝一個異步操作,避免回調(diào)地獄。
- 有三種狀態(tài):
pending(等待中)、fulfilled(已完成)、rejected(已拒絕) - 通過
.then()/.catch()鏈?zhǔn)教幚斫Y(jié)果。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true
success ? resolve("數(shù)據(jù)加載成功") : reject("失敗")
}, 1000)
})
}
fetchData()
.then((data) => console.log(data))
.catch((err) => console.error(err))
async/await
- 是
Promise的語法糖,讓異步代碼寫起來像同步代碼。 - 只能在
async函數(shù)中使用。 - 使用
try/catch更方便地處理異常。
async function getData() {
try {
const data = await fetchData()
console.log("結(jié)果:", data)
} catch (err) {
console.error("出錯了:", err)
}
}
getData()
總結(jié)
| 技術(shù)/特性 | 描述 |
|---|---|
| 單線程模型 | JS 默認(rèn)僅一個主線程,任務(wù)順序執(zhí)行 |
| 異步操作 | 不阻塞主線程,通過事件隊(duì)列執(zhí)行回調(diào) |
| Web Worker | 瀏覽器中模擬多線程,適合重任務(wù) |
| Node WorkerThreads | 后端的多線程計(jì)算方案 |
| 任務(wù)拆分 | 將大任務(wù)拆成小塊,分幀執(zhí)行減輕壓力 |
| Promise | 管理異步邏輯,避免回調(diào)地獄 |
| async/await | 讓異步代碼更像同步,提升可讀性 |
總之,單線程就像收銀臺只有一個店員,但現(xiàn)代網(wǎng)頁用各種辦法讓這個店員手腳麻利到飛起。
到此這篇關(guān)于淺談JavaScript 單線程防阻塞的原理的文章就介紹到這了,更多相關(guān)JavaScript 單線程防阻塞內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序?qū)崿F(xiàn)幸運(yùn)大轉(zhuǎn)盤功能的示例代碼
這篇文章主要介紹了如何在微信小程序中實(shí)現(xiàn)幸運(yùn)大轉(zhuǎn)盤功能,參與用戶點(diǎn)擊抽獎便可抽取輪盤的獎品,感興趣的小伙伴可以跟隨小編一起動手試一試2022-03-03
三種在ES6中將非數(shù)組轉(zhuǎn)換為數(shù)組的方法詳情
這篇文章主要介紹了三種在ES6中將非數(shù)組轉(zhuǎn)換為數(shù)組的方法詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-08-08
JavaScript實(shí)現(xiàn)簡單的計(jì)算器
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)簡單的計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-01-01
收集的一些Array及String原型對象的擴(kuò)展實(shí)現(xiàn)代碼
收集的一些Array及String原型對象的擴(kuò)展實(shí)現(xiàn)代碼,學(xué)習(xí)js的朋友可以參考下。并可以自定義的對字符串與array數(shù)據(jù),進(jìn)行擴(kuò)展。2010-12-12
JavaScript?數(shù)據(jù)結(jié)構(gòu)之字典方法
這篇文章主要介紹了JavaScript?數(shù)據(jù)結(jié)構(gòu)之字典方法,字典即Dictionary,是一種以?鍵-值對?形式存儲數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu),下文更多相關(guān)介紹需要的小伙伴可以參考一下2022-04-04

