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

四、任務(wù)類型詳解
1. 宏任務(wù)(Macrotask)
| 來源 | 示例 |
|---|---|
| setTimeout/setInterval | setTimeout(fn, 0) |
| I/O 操作 | 文件讀寫、網(wǎng)絡(luò)請(qǐng)求 |
| UI 渲染 | (瀏覽器) |
| 事件回調(diào) | click, scroll 等 |
| setImmediate | (Node.js 特有) |
特點(diǎn):
- 每次事件循環(huán)只執(zhí)行一個(gè)宏任務(wù)
- 優(yōu)先級(jí)低于微任務(wù)
2. 微任務(wù)(Microtask)
| 來源 | 示例 |
|---|---|
| Promise | .then()/.catch() |
| MutationObserver | DOM 變更觀察 |
| process.nextTick | (Node.js 特有,優(yōu)先級(jí)最高) |
特點(diǎn):
- 在當(dāng)前宏任務(wù)結(jié)束后立即執(zhí)行
- 會(huì)清空整個(gè)微任務(wù)隊(duì)列
- 優(yōu)先級(jí)高于宏任務(wù)
五、經(jīng)典執(zhí)行順序示例
console.log('1. 同步代碼開始');
setTimeout(() => {
console.log('6. 宏任務(wù)1 - setTimeout');
Promise.resolve().then(() => {
console.log('7. 微任務(wù)3 - Promise');
});
}, 0);
Promise.resolve().then(() => {
console.log('3. 微任務(wù)1 - Promise');
return Promise.resolve();
}).then(() => {
console.log('4. 微任務(wù)2 - Promise');
});
console.log('2. 同步代碼結(jié)束');
// 輸出順序:
// 1. 同步代碼開始
// 2. 同步代碼結(jié)束
// 3. 微任務(wù)1 - Promise
// 4. 微任務(wù)2 - Promise
// 6. 宏任務(wù)1 - setTimeout
// 7. 微任務(wù)3 - Promise
六、實(shí)際應(yīng)用場(chǎng)景
1. 定時(shí)任務(wù)控制
// 動(dòng)畫幀控制
function animate() {
// 動(dòng)畫邏輯
requestAnimationFrame(animate); // 比setTimeout更適合動(dòng)畫
}
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 => {
// 錯(cuò)誤處理
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(() => {
// 處理滾動(dòng)邏輯
}, 100));
七、常見誤區(qū)與最佳實(shí)踐
1. 不要阻塞事件循環(huán)
// 錯(cuò)誤示范:同步計(jì)算阻塞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. 微任務(wù)嵌套陷阱
// 可能導(dǎo)致無限循環(huán)
function microtaskLoop() {
Promise.resolve().then(microtaskLoop);
}
// microtaskLoop(); // 不要這樣做!
3. 合理使用任務(wù)優(yōu)先級(jí)
// 需要立即執(zhí)行的任務(wù)使用微任務(wù)
function urgentTask(callback) {
Promise.resolve().then(callback);
}
// 不緊急的任務(wù)使用宏任務(wù)
function backgroundTask(callback) {
setTimeout(callback, 0);
}
總結(jié)
到此這篇關(guān)于JavaScript事件循環(huán)深入講解及常見誤區(qū)的文章就介紹到這了,更多相關(guān)JS事件循環(huán)詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascripit實(shí)現(xiàn)密碼強(qiáng)度檢測(cè)代碼分享
這篇文章主要介紹了javascripit實(shí)現(xiàn)密碼強(qiáng)度檢測(cè),大家參考使用吧2013-12-12
Boostrap模態(tài)窗口的學(xué)習(xí)小結(jié)
Bootstrap Modals(模態(tài)框)是使用定制的 Jquery 插件創(chuàng)建的。它可以用來創(chuàng)建模態(tài)窗口豐富用戶體驗(yàn),或者為用戶添加實(shí)用功能。您可以在 Modals(模態(tài)框)中使用 Popover(彈出框)和 Tooltip(工具提示插件)2016-03-03
javascript css styleFloat和cssFloat
在寫js操作css的過程中發(fā)現(xiàn)float屬性在IE和firefox下對(duì)應(yīng)的js腳本是不一樣的,IE下對(duì)應(yīng)得是 styleFloat,firefox,chorme,safari下對(duì)應(yīng)的是cssFloat,可用in運(yùn)算符去檢測(cè)style是否包含此屬性。2010-03-03
利用原生JS與jQuery實(shí)現(xiàn)數(shù)字線性變化的動(dòng)畫
最近在工作中遇到一個(gè)需要,需要將數(shù)字實(shí)現(xiàn)遞增的動(dòng)態(tài)顯示,從網(wǎng)上找了相關(guān)的資料發(fā)現(xiàn)利用原生JS與jQuery都可以實(shí)現(xiàn),suoyi8下面這篇文章主要給大家介紹了利用原生JS與jQuery實(shí)現(xiàn)數(shù)字線性變化動(dòng)畫的相關(guān)資料,需要的朋友可以參考下。2017-02-02
javascript GUID生成器實(shí)現(xiàn)代碼
javascript GUID生成器實(shí)現(xiàn)代碼, 需要的朋友可以參考下。2009-10-10
理運(yùn)用命名空間讓js不產(chǎn)生沖突避免全局變量的泛濫
為了避免變量之間的覆蓋與沖突,可以生成命名空間,命名空間是一種特殊的前綴,在不同的匿名函數(shù)中,根據(jù)功能聲明一個(gè)不同的命名空間2014-06-06
js微信應(yīng)用場(chǎng)景之微信音樂相冊(cè)案例分享
這篇文章主要為大家分享了js微信應(yīng)用場(chǎng)景之微信音樂相冊(cè)案例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
文章或博客自動(dòng)生成章節(jié)目錄索引(支持三級(jí))的實(shí)現(xiàn)代碼
一個(gè)好的博文除了博文的質(zhì)量要好以外,好的組織結(jié)構(gòu)也能讓讀者閱讀的更加舒服與方便,我看很多網(wǎng)站里面有一些園友的博文都是分章節(jié)的,并且在博文的前面都帶有章節(jié)的目錄索引,點(diǎn)擊索引之后會(huì)跳轉(zhuǎn)到相應(yīng)的章節(jié)閱讀2020-05-05

