JavaScript事件循環(huán)深入講解及常見誤區(qū)
一、JS 的單線程模型與異步機制
JS 是一種單線程語言,這意味著它只有一個主線程(執(zhí)行棧)來處理所有任務。這種設計避免了多線程環(huán)境中的復雜同步問題,但也帶來了一個挑戰(zhàn):如何防止長時間運行的代碼阻塞整個程序?
解決方案是將代碼分為:
- 同步代碼:由 JS 引擎直接執(zhí)行
- 異步代碼:交給宿主環(huán)境(瀏覽器/Node.js)處理
二、事件循環(huán)的核心組件
1. 執(zhí)行棧(Call Stack)
- 用于存儲同步任務的執(zhí)行上下文
- 遵循后進先出(LIFO)原則
- 當函數(shù)執(zhí)行時會被推入棧頂,執(zhí)行完畢后彈出
2. 任務隊列(Task Queue)
- 宏任務隊列(Macrotask Queue)
- 微任務隊列(Microtask Queue)
3. Web APIs
- 瀏覽器提供的異步API(setTimeout、DOM事件等)
- Node.js 中的 I/O 操作等
三、事件循環(huán)的執(zhí)行流程
執(zhí)行同步代碼:執(zhí)行棧中的任務依次執(zhí)行
處理微任務:
- 執(zhí)行棧清空后,立即執(zhí)行所有微任務
- 微任務執(zhí)行期間產生的新微任務會繼續(xù)執(zhí)行
渲染更新(瀏覽器環(huán)境)
取一個宏任務執(zhí)行
重復循環(huán)

四、任務類型詳解
1. 宏任務(Macrotask)
| 來源 | 示例 |
|---|---|
| setTimeout/setInterval | setTimeout(fn, 0) |
| I/O 操作 | 文件讀寫、網(wǎng)絡請求 |
| UI 渲染 | (瀏覽器) |
| 事件回調 | click, scroll 等 |
| setImmediate | (Node.js 特有) |
特點:
- 每次事件循環(huán)只執(zhí)行一個宏任務
- 優(yōu)先級低于微任務
2. 微任務(Microtask)
| 來源 | 示例 |
|---|---|
| Promise | .then()/.catch() |
| MutationObserver | DOM 變更觀察 |
| process.nextTick | (Node.js 特有,優(yōu)先級最高) |
特點:
- 在當前宏任務結束后立即執(zhí)行
- 會清空整個微任務隊列
- 優(yōu)先級高于宏任務
五、經典執(zhí)行順序示例
console.log('1. 同步代碼開始');
setTimeout(() => {
console.log('6. 宏任務1 - setTimeout');
Promise.resolve().then(() => {
console.log('7. 微任務3 - Promise');
});
}, 0);
Promise.resolve().then(() => {
console.log('3. 微任務1 - Promise');
return Promise.resolve();
}).then(() => {
console.log('4. 微任務2 - Promise');
});
console.log('2. 同步代碼結束');
// 輸出順序:
// 1. 同步代碼開始
// 2. 同步代碼結束
// 3. 微任務1 - Promise
// 4. 微任務2 - Promise
// 6. 宏任務1 - setTimeout
// 7. 微任務3 - Promise
六、實際應用場景
1. 定時任務控制
// 動畫幀控制
function animate() {
// 動畫邏輯
requestAnimationFrame(animate); // 比setTimeout更適合動畫
}
animate();
// 輪詢檢查
function poll() {
fetch('/api/status')
.then(checkStatus)
.then(() => setTimeout(poll, 5000));
}
2. Promise 異步流程
function loadData() {
return fetch('/api/data')
.then(response => response.json())
.then(data => {
// 處理數(shù)據(jù)
return processData(data);
})
.catch(error => {
// 錯誤處理
console.error(error);
});
}
3. DOM 事件優(yōu)化
// 防抖處理高頻事件
function debounce(fn, delay) {
let timer;
return function() {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, arguments), delay);
};
}
window.addEventListener('scroll', debounce(() => {
// 處理滾動邏輯
}, 100));
七、常見誤區(qū)與最佳實踐
1. 不要阻塞事件循環(huán)
// 錯誤示范:同步計算阻塞UI
function heavyCalc() {
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += Math.sqrt(i);
}
return result;
}
// 正確做法:分片處理
async function chunkedHeavyCalc() {
let result = 0;
for (let i = 0; i < 100000000; i += 100000) {
result += await chunkCalc(i, Math.min(i + 100000, 100000000));
// 允許瀏覽器渲染
await new Promise(resolve => requestAnimationFrame(resolve));
}
return result;
}
2. 微任務嵌套陷阱
// 可能導致無限循環(huán)
function microtaskLoop() {
Promise.resolve().then(microtaskLoop);
}
// microtaskLoop(); // 不要這樣做!
3. 合理使用任務優(yōu)先級
// 需要立即執(zhí)行的任務使用微任務
function urgentTask(callback) {
Promise.resolve().then(callback);
}
// 不緊急的任務使用宏任務
function backgroundTask(callback) {
setTimeout(callback, 0);
}
總結
到此這篇關于JavaScript事件循環(huán)深入講解及常見誤區(qū)的文章就介紹到這了,更多相關JS事件循環(huán)詳解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JSON.parse處理非標準Json數(shù)據(jù)出錯的解決
這篇文章主要介紹了JSON.parse處理非標準Json數(shù)據(jù)出錯的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09
js實現(xiàn)首屏延遲加載實現(xiàn)方法 js實現(xiàn)多屏單張圖片延遲加載效果
這篇文章主要介紹了js實現(xiàn)首屏延遲加載實現(xiàn)方法,以及js實現(xiàn)多屏單張圖片延遲加載效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07
uniapp踩坑實戰(zhàn)之文件查找失敗:'uview-ui'?at?main.js解決辦法
這篇文章主要給大家介紹了關于uniapp踩坑實戰(zhàn)之文件查找失敗:'uview-ui'?at?main.js的解決辦法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2023-12-12

