本文同步分享於個人部落格:Liwen Chiou | Digital Architect & Full-Stack Engineer
孩子,在踏上今日的領地前,先坐下來喝杯酒吧。
修煉的路很長,別急著奔跑,先聽聽這份卷軸裡的古老叮嚀。我在每一站的盡頭都為你鋪設了 「
實戰演武場 CodePen」。那不只是程式碼的拼湊,而是你在這場暴風雨中,試圖用邏輯點燃的一顆星火。
成功突圍後,別忘了帶著你的戰利品回到 「QuestBoard 佈告欄」。在那裡,你會發現自己從不孤單,公會的夥伴們都正舉杯等待你的歸來。記住...
「 導師看重的從來不是你的劍法有多華麗,而是當你被邏輯擊潰、滿身泥濘後,依然選擇握緊理性的劍柄,再次向真相發起衝鋒的勇氣。 」
「導師,這不可能!我明明在第一行寫了 setTimeout(..., 0),為什麼它卻在我最後一行的 console 之後才印出來?0 秒難道不是立刻嗎?」
我放下手中的羽毛筆,看著眼前焦慮的新手冒險者。
「孩子,這就是 JavaScript 最誠服也最神祕的本質。它是一個 『單線跑者』。即便你給他一百個任務,他的大腦一次也只能思考一件事。今天,我們要潛入 JS 的思維深處,看看那位忙碌的 『辦事員』 是如何與 『自動販賣機 (Web APIs)』 以及 『排隊區 (Queue)』 協作,才不至於讓整個網頁崩潰。」
這章節,我們不學語法,我們要學「看清時間的流向」。
JavaScript 的核心引擎(Call Stack)就像一個狹窄的單向櫃檯。不論是運算、選取 DOM、還是跑迴圈,辦事員一次只能處理一個動作。
如果有一個任務太耗時(例如:去圖書館查閱一萬卷宗),辦事員如果停下來等,整個公會(網頁)就會像凍結了一樣,誰也點不動。
為了不讓櫃檯卡死,JS 發展出一套聰明的機制:
🏹 導師的辨析圖:執行順序預測
| 動作 | 代碼類型 | 處理地點 |
|---|---|---|
| console.log | 同步任務 | 立即在櫃檯執行 |
| setTimeout | 非同步任務 | 送往販賣機計時,跑完後去排隊區 |
| for 迴圈 (百萬次) | 阻塞任務 | 強佔櫃檯,直到跑完為止 |
導師的圖解心法: 想像辦事員只有一隻手。他遇到
setTimeout時,會先把計時器丟給旁邊的「自動販賣機」代管。只有當他的櫃檯完全清空 (Stack Empty) 時,他才會轉頭看向「排隊區」,把排在第一位的任務抓回櫃檯執行。這就是為什麼setTimeout(..., 0)依然要排隊。

console.log("A: 開始修煉"); // 同步
setTimeout(() => {
console.log("B: 計時炸彈爆了"); // 非同步
}, 0);
console.log("C: 結束修煉"); // 同步
導師講評: 為什麼 C 會比 B 快?因為 B 被丟進了「計時販賣機」,即便只有 0 秒,他也要在「排隊區」等辦事員處理完剩餘的 C 之後,才有機會回到櫃檯。
如果你寫了一個 for (let i=0; i<1000000000; i++),辦事員會在那裡死命地數數,這段時間他無法轉頭看「排隊區」,也無法處理任何使用者點擊。這就是為什麼網頁會出現「程式沒有回應」的原因。
職人金句: 絕不要在櫃檯做太久的體力活,學會把重體力活拆散,或是交給非同步處理。
孩子,導師當年以為
setTimeout(..., 1000)真的是「精準 1 秒後執行」。
結果那次我的櫃檯正忙著處理一個巨大的地圖檔案,處理了 3 秒,導致我的計時器在 4 秒後才印出來。記住:JavaScript 的時間不是準確的約定,它只是「最快何時能跑」的保證。
天色微亮,營火雖已燃盡,但你眼中卻閃爍著領悟的光芒。在前往演武場挑戰關卡前,我已經幫你把靈魂碎片精煉成了這份「戰術錦囊」。拿好它,今日的任務不再是負擔,而是你證明自我的舞台。去吧,公會的英雄榜在等著你的戰報。
setTimeout 裡面再寫一個 setTimeout,誰會先跑?alert() 會讓整個網頁的所有計時器都暫停嗎?(因為它強佔了哪一個區塊?)