LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

深入淺出 Event Loop:前端工程師必須掌握的運(yùn)行機(jī)制

freeflydom
2025年8月8日 10:56 本文熱度 484

今天想和大家聊聊一個在面試中幾乎必問,但在實(shí)際工作中又容易被忽略的話題——Event Loop(事件循環(huán))

你可能在面試時被問過:“JavaScript 是單線程的,那它是怎么實(shí)現(xiàn)異步的?”或者“setTimeout(fn, 0) 真的是立即執(zhí)行嗎?”這些問題的答案,都藏在 Event Loop 里。

別擔(dān)心,即使你是第一次接觸這個概念,我也盡量用最通俗的方式,帶你一步步搞懂它。我們不堆術(shù)語,不講玄學(xué),只說人話。


一、JavaScript 的“單線程”到底是什么意思?

我們常說 JavaScript 是“單線程”的,這句話到底意味著什么?

你可以把 JavaScript 的執(zhí)行環(huán)境想象成一個只有一位服務(wù)員的快餐店。這位服務(wù)員(也就是主線程)一次只能處理一件事:點(diǎn)餐、做漢堡、收錢……他不能同時做兩件事。

比如,你讓服務(wù)員“做一份漢堡,同時給我一杯可樂”,他只能先做漢堡,再倒可樂,或者反過來。他不能一邊煎肉餅一邊倒飲料。

在代碼里,這就像這樣:

console.log("第一步");
console.log("第二步");
setTimeout(() => {
  console.log("第三步(異步)");
}, 1000);
console.log("第四步");

輸出結(jié)果是:

第一步
第二步
第四步
第三步(異步)

你看,setTimeout 雖然寫在第三步,但它并沒有立刻執(zhí)行,而是被“推遲”了。為什么?因?yàn)橹骶€程要先把當(dāng)前的任務(wù)做完,才能回頭處理它。

這就是“單線程”的核心:同一時間,只能做一件事。


二、那異步是怎么實(shí)現(xiàn)的?總不能一直卡著吧?

既然 JS 是單線程的,那像 setTimeout、fetch、addEventListener 這些異步操作是怎么做到不阻塞主線程的呢?

答案是:它們不是 JS 自己做的,而是瀏覽器(或 Node.js 環(huán)境)幫我們做的。

繼續(xù)用快餐店的比喻:

  • 服務(wù)員(JS 主線程)負(fù)責(zé)點(diǎn)單和出餐。
  • 但廚房里的烤箱、冰箱、飲料機(jī)……這些是“瀏覽器提供的能力”。
  • 當(dāng)你點(diǎn)了一個漢堡,服務(wù)員不會自己去煎,而是把訂單交給廚房(異步任務(wù)),然后繼續(xù)服務(wù)下一位顧客。
  • 等廚房做好了,會通知服務(wù)員:“你的漢堡好了”,服務(wù)員再把漢堡端給你。

在技術(shù)上,這個“通知”機(jī)制就是通過 任務(wù)隊(duì)列(Task Queue) 實(shí)現(xiàn)的。


三、Event Loop 的三大核心:調(diào)用棧、任務(wù)隊(duì)列、事件循環(huán)

要理解 Event Loop,你需要知道三個關(guān)鍵角色:

1. 調(diào)用棧(Call Stack)

這是 JS 執(zhí)行函數(shù)的地方。你可以把它想象成一個“待辦事項(xiàng)清單”,從上到下依次執(zhí)行。

比如這段代碼:

function a() {
  b();
  console.log("a 執(zhí)行完了");
}
function b() {
  console.log("b 開始執(zhí)行");
}
a();

執(zhí)行過程就像這樣:

  • a() 被推入調(diào)用棧
  • a 里面調(diào)用 b(),b() 被推入棧
  • b() 執(zhí)行完,從棧中彈出
  • a() 繼續(xù)執(zhí)行,打印“a 執(zhí)行完了”,然后彈出

調(diào)用棧是“同步任務(wù)”的執(zhí)行場所。

2. 任務(wù)隊(duì)列(Task Queue / Callback Queue)

當(dāng)異步任務(wù)(比如 setTimeoutsetInterval、DOM 事件、Ajax 請求)完成時,它們的回調(diào)函數(shù)不會立刻執(zhí)行,而是被放進(jìn)一個“等待區(qū)”——這就是任務(wù)隊(duì)列。

任務(wù)隊(duì)列是一個先進(jìn)先出(FIFO) 的隊(duì)列。先進(jìn)來的回調(diào),先被執(zhí)行。

3. 事件循環(huán)(Event Loop)

這才是真正的“調(diào)度員”。它的工作非常簡單:

不斷檢查調(diào)用棧是否為空。如果為空,就從任務(wù)隊(duì)列里取出第一個回調(diào),推入調(diào)用棧執(zhí)行。

就這么簡單!它像個永不停歇的循環(huán),一直盯著:

  • “??樟藛??”
  • “空了?好,看看隊(duì)列里有沒有任務(wù)。”
  • “有?拿一個過來執(zhí)行?!?/li>

這就是“事件循環(huán)”名字的由來:它在循環(huán)地處理事件(回調(diào))。


四、宏任務(wù) vs 微任務(wù):你必須知道的細(xì)節(jié)

到這里,你以為 Event Loop 就完了?不,還有一個更精細(xì)的劃分:宏任務(wù)(Macrotask)和微任務(wù)(Microtask)。

1. 宏任務(wù)(Macrotask)

常見的宏任務(wù)包括:

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • I/O 操作
  • UI 渲染(瀏覽器)
  • script 標(biāo)簽中的整體代碼

2. 微任務(wù)(Microtask)

微任務(wù)的優(yōu)先級更高,常見的有:

  • Promise.then/catch/finally
  • MutationObserver(監(jiān)聽 DOM 變化)
  • queueMicrotask()
  • process.nextTick()(Node.js)

關(guān)鍵區(qū)別:執(zhí)行時機(jī)

Event Loop 的執(zhí)行順序是這樣的:

  1. 執(zhí)行一個宏任務(wù)(比如整個 script 代碼)
  2. 執(zhí)行過程中,遇到異步操作,把回調(diào)放進(jìn)對應(yīng)的隊(duì)列:
    • setTimeout → 宏任務(wù)隊(duì)列
    • Promise.then → 微任務(wù)隊(duì)列
  3. 當(dāng)前宏任務(wù)執(zhí)行完,立即清空微任務(wù)隊(duì)列(全部執(zhí)行完)
  4. 然后去宏任務(wù)隊(duì)列取下一個宏任務(wù)
  5. 重復(fù)這個過程

舉個例子,徹底搞懂

來看這段經(jīng)典代碼:

console.log("1");
setTimeout(() => {
  console.log("2");
}, 0);
Promise.resolve().then(() => {
  console.log("3");
});
console.log("4");

輸出順序是什么?

我們一步步分析:

  1. 執(zhí)行全局腳本(宏任務(wù))

    • 打印 "1"
    • 遇到 setTimeout,把回調(diào) () => console.log("2") 放入宏任務(wù)隊(duì)列
    • 遇到 Promise.then,把回調(diào) () => console.log("3") 放入微任務(wù)隊(duì)列
    • 打印 "4"
    • 當(dāng)前宏任務(wù)執(zhí)行完畢
  2. 清空微任務(wù)隊(duì)列

    • 執(zhí)行 Promise.then 的回調(diào),打印 "3"
  3. 取下一個宏任務(wù)

    • 執(zhí)行 setTimeout 的回調(diào),打印 "2"

所以輸出是:1 → 4 → 3 → 2

注意:setTimeout(fn, 0) 并不是“立即執(zhí)行”,而是“等當(dāng)前所有同步和微任務(wù)執(zhí)行完后,再執(zhí)行”。


五、更復(fù)雜的例子:嵌套 Promise 和 setTimeout

再看一個稍微復(fù)雜點(diǎn)的例子:

console.log("start");
setTimeout(() => {
  console.log("timeout1");
  Promise.resolve().then(() => {
    console.log("promise in timeout");
  });
}, 0);
Promise.resolve().then(() => {
  console.log("promise1");
  setTimeout(() => {
    console.log("timeout in promise");
  }, 0);
});
console.log("end");

來,我們一步步走:

  1. 執(zhí)行全局宏任務(wù):

    • 打印 "start"
    • setTimeout → 宏任務(wù)隊(duì)列
    • Promise.then → 微任務(wù)隊(duì)列
    • 打印 "end"
    • 宏任務(wù)結(jié)束
  2. 清空微任務(wù)隊(duì)列:

    • 執(zhí)行 Promise.then,打印 "promise1"
    • 在 then 里又遇到 setTimeout,把它加入宏任務(wù)隊(duì)列
    • 微任務(wù)隊(duì)列清空
  3. 取下一個宏任務(wù)(第一個 setTimeout):

    • 執(zhí)行,打印 "timeout1"
    • 遇到 Promise.then,加入微任務(wù)隊(duì)列
  4. 清空微任務(wù)隊(duì)列:

    • 執(zhí)行 Promise.then,打印 "promise in timeout"
  5. 取下一個宏任務(wù)(Promise.then 里的 setTimeout):

    • 執(zhí)行,打印 "timeout in promise"

最終輸出:

start
end
promise1
timeout1
promise in timeout
timeout in promise

是不是有點(diǎn)繞?多看幾遍,畫個流程圖,就清楚了。


六、為什么要有微任務(wù)?它有什么用?

你可能會問:既然有宏任務(wù)就夠了,為啥還要搞個微任務(wù)?

答案是:為了更精細(xì)的控制和性能優(yōu)化。

比如:

  • Promise 的鏈?zhǔn)秸{(diào)用.then().then().then(),我們希望這些回調(diào)能盡快執(zhí)行,而不是等一輪完整的 Event Loop。
  • 避免 UI 卡頓:微任務(wù)在當(dāng)前任務(wù)結(jié)束后立即執(zhí)行,不會觸發(fā)頁面重繪,適合做數(shù)據(jù)更新、狀態(tài)同步等操作。
  • DOM 觀察MutationObserver 用微任務(wù)來批量處理 DOM 變化,避免頻繁重排。

簡單說:微任務(wù) = 高優(yōu)先級、立即執(zhí)行的小任務(wù)


七、實(shí)際開發(fā)中的影響

理解 Event Loop 不只是應(yīng)付面試,它對實(shí)際開發(fā)也有幫助。

1. 避免長時間同步任務(wù)阻塞 UI

// ? 千萬別這么干!
for (let i = 0; i < 1000000; i++) {
  // 做大量計(jì)算
}
// 這期間頁面完全卡死,用戶無法點(diǎn)擊、滾動

這種長時間運(yùn)行的同步代碼會阻塞 Event Loop,導(dǎo)致頁面無響應(yīng)。

解決方案:拆分成小任務(wù),用 setTimeout 或 requestIdleCallback 分批執(zhí)行。

2. 正確處理異步依賴

let data;
fetch("/api/data").then(res => res.json()).then(d => data = d);
console.log(data); // undefined!

因?yàn)?nbsp;fetch 是異步的,console.log 是同步的,它先執(zhí)行了。

正確做法:用 async/await 或確保在回調(diào)中使用數(shù)據(jù)。

3. setTimeout(fn, 0) 的用途

雖然它不是“立即執(zhí)行”,但可以用來:

  • 將任務(wù)推遲到下一輪 Event Loop
  • 讓 UI 有機(jī)會先更新
  • 實(shí)現(xiàn)簡單的“異步批處理”
// 比如:收集一批操作,最后統(tǒng)一處理
let queue = [];
function addTask(task) {
  queue.push(task);
  setTimeout(processQueue, 0); // 推遲到下一輪
}
function processQueue() {
  if (queue.length > 0) {
    // 處理所有任務(wù)
    queue.forEach(task => task());
    queue = [];
  }
}

八、總結(jié):Event Loop 的完整流程

最后,我們來梳理一下瀏覽器中 Event Loop 的完整流程:

  1. 執(zhí)行一個宏任務(wù)(如整個 script
  2. 執(zhí)行過程中:
    • 遇到 setTimeout → 加入宏任務(wù)隊(duì)列
    • 遇到 Promise.then → 加入微任務(wù)隊(duì)列
    • 遇到 DOM 事件 → 加入宏任務(wù)隊(duì)列
  3. 當(dāng)前宏任務(wù)執(zhí)行完畢
  4. 立即執(zhí)行所有微任務(wù)(清空微任務(wù)隊(duì)列)
  5. 嘗試渲染頁面(如果需要)
  6. 取下一個宏任務(wù),回到第1步

記住這個口訣:

一個宏任務(wù),清空微任務(wù),再來下一個宏任務(wù)。


寫在最后

Event Loop 是 JavaScript 異步編程的基石。它看似復(fù)雜,但核心思想很簡單:用一個循環(huán)不斷檢查任務(wù)隊(duì)列,按順序執(zhí)行任務(wù)

?轉(zhuǎn)自https://juejin.cn/post/7534907614394482723


該文章在 2025/8/8 10:56:38 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
亚洲中文字幕五码专区 | 亚洲欧美自拍另类欧美亚洲中文字 | 亚洲最大激情中文字幕 | 亚洲国产午夜福利在线视频 | 亚洲一区国产美女在线速度快 | 制服.丝袜.亚洲.中文.综合 |