JavaScript 執行模型:從 event loop 到 async/await 的一條推理鏈
文字解析 · 1 支影片 · 產生於 2026-10-01 00:54
章節(9)
1. Outline
- 起點 · 9 JavaScript Concepts That Got Me To Senior Dev
把「精通基礎」拆成一條可驗收的鏈:event loop 決定執行順序 → execution context 與 scope chain 決定函式看得到什麼 → closure 與 GC 決定記憶體何時釋放 → Promise 解釋誰把任務丟進 micro-task queue → async/await 是它的語法糖 → generator 是語法糖的另一半。
2. YouTuber 的思維推導
9 JavaScript Concepts That Got Me To Senior Dev
theSeniorDev · 20m11s · 字幕 en · vision=on (整支影片是白板式動畫圖解 + 程式碼截圖,作者反覆說「looking at this piece of code」「as you see here」「going back to our promise chain」,說明高度依賴畫面;不看圖無法對照 call stack / microtask queue 的移動與 scope chain 的巢狀關係。) · YouTube
預估 vs 實際耗時
| 階段 | 預估 | 實際 |
|---|---|---|
| fetch | 6s | 2s |
| segment | 1m10s | – |
| shot | 49s | 6s |
| analyze | 8m45s | – |
| render | 5s | – |
作者的推導是一條「由執行機制往上長」的鏈,而不是九個並列的知識點。他先問「JavaScript 到底是誰在執行、憑什麼決定順序」,答案是 event loop,於是先把 call stack、macro-task queue、micro-task queue 的全景圖畫出來當作全片的座標系。接著他不讓你停在圖上,立刻丟一題 console.log 順序的面試題,逼你用那張圖去預測輸出——這是他反覆使用的手法:先給機制,再用一段程式碼驗收。
驗收過程中他發現推演時把函式當成一個黑盒推進 call stack,於是往下鑽一層問「一個 stack frame 裡到底裝了什麼」,帶出 execution context、scope 與 scope chain;scope chain 決定了函式看得見哪些變數,自然接到「那函式活著的時候這些變數會不會被回收」,於是 closure 與 garbage collection 成為同一個問題的兩面。講完記憶體,他把鏡頭拉回一開始那張圖上還沒解釋的 micro-task queue:是誰把東西丟進去?答案要從歷史講起——callback 時代、callback hell、Promise 作為控制權反轉的解法,而 Promise 的 callback 正是進 micro-task queue 的東西,圖於是閉合。
最後兩層是語法層的收束:async/await 是 Promise 的語法糖,而 async/await 的底層又是 Promise 加上一個可以暫停的函式,也就是 generator。全片的說服力來自這個結構——每個概念都是為了回答前一個概念留下的缺口,而不是因為它出現在面試題庫裡。
推理鏈:每段留給下一段的線索
每一列是一段,箭頭後的字是這段留給下一段的線索。點段落標題跳到該段。
- 問題設定:沒人說清楚的「基礎」 → 留下一個具體問題:JavaScript 程式碼到底是「誰」在執行、憑什麼決定哪一行先跑?這正是下一段 event loop 要回答的。
- Event loop 的整體架構 → 圖看懂了不等於會用。留下的問題是:拿一段同時有 Promise 與 setTimeout 的真實程式碼,這張圖能不能準確預測 console 的輸出順序?
- 用 console.log 順序驗證 event loop → 推演時我們把每個函式當成一個方塊推進 call stack。留下的問題是:那個方塊裡面到底裝了什麼?執行一個函式需要的東西不只是函式本身。
- Execution context 與 scope chain → scope chain 決定了一個函式看得見哪些變數。留下的問題是:既然函式記得這些變數,那當函式還活著時,這些變數的記憶體還能不能被釋放?
- Closure 與垃圾回收 → 記憶體的問題收束了,但第 2 段那張圖上還有一個沒交代的角色:micro-task queue 裡的東西究竟是誰放進去的?答案要從 Promise 出現之前的年代講起。
- 從 callback hell 到 Promise → Promise chain 比 callback hell 乾淨,但一長串 .then 讀起來仍然是巢狀的。留下的問題是:能不能讓非同步程式碼長得跟同步程式碼一模一樣?
- async/await 是 Promise 的語法糖 → 作者說 async 是「Promise 加上某種能暫停的函式」。留下的問題是:JavaScript 裡什麼樣的函式可以執行到一半停下來、之後再從斷點繼續?
- Generator:async/await 的另一半 → 九個概念到這裡連成了一條鏈:從誰執行程式碼,一路推到最上層的語法糖。留下的問題是——這些語言層的知識,該放進整個前端系統的哪個位置?
- 結語與下一步
3. 逐段說明
9 JavaScript Concepts That Got Me To Senior Dev
1. 問題設定:沒人說清楚的「基礎」 0:00–0:22
作者指出大家都說要升 senior 就得精通基礎,卻沒人講清楚基礎到底是哪些。他把問題收斂成三個檢驗標準:這些概念是什麼、面試怎麼用、寫 production code 時真的有差嗎。接著宣告本片要講九個每位資深工程師都掌握的 JavaScript 概念,並從 event loop 開始。
推理因為讀者已經聽膩了「要精通基礎」這句空話,所以作者第一步不是講概念,而是先幫這句話定義驗收標準:哪些概念算基礎、在面試怎麼被問、寫 production code 時到底有沒有差。有了標準,後面九個概念才不是任意的清單。
AI 補充值得注意他挑的三個檢驗標準其實是同一件事的三個切面:面試題之所以問這些,正是因為它們是「無法用查文件補救」的執行模型知識。API 名稱可以查,但「這行為什麼比那行先印」只能靠腦中有一張正確的模型圖。這也解釋了為什麼整支片的第一個概念是 event loop 而不是語法——他要先給你那張圖。
2. Event loop 的整體架構 0:22–2:28
Event loop 是實際執行 JavaScript 的元件,由 call stack、microtask queue 與 macrotask queue 三部分組成,且佇列有優先序。這一切發生在瀏覽器環境裡:DOM 元素上掛的 event listener、Timer API、Promise API 都會把 task 推進佇列;佇列是 FIFO。一次 tick 的流程是:task 進 call stack 拆成 function call 執行,call stack 清空後在結束這次 tick 前先清 microtask queue,然後瀏覽器 render,再跑下一次迭代。



推理因為上一段把問題設定成「執行順序從哪裡來」,所以作者接著不講語法而是畫架構圖:先立 call stack,再加上兩個佇列,最後把瀏覽器環境(DOM、event listener、Timer API、Promise API)接上去,讓「任務從哪裡來、往哪裡去」變成一條可以用手指跟著走的動線。
- CEvent Loop:call stack 清空後先清光 micro-task queue,再從 macro-task queue 取一個→ 畫地圖
- CMacro-task Queue:裝 DOM event、Timer API 的 callback,每輪 event loop 只取一個出來跑→ 畫地圖
- CMicro-task Queue:裝 Promise 的 callback,本輪內清空為止,途中新加的也會被吃掉→ 畫地圖
- E字幕把 macro-task queue 誤聽成 micro-task queue,要對照畫面上紅/橘兩個佇列修正→ 存+演練
- RCall Stack:記錄目前正在執行的函式呼叫,LIFO→ 存+回想
AI 補充字幕在這裡有明顯的辨識錯誤,把 macro-task queue 全部聽成 micro-task queue(「we have the microtask Q and the microtask Q」)。對照畫面就清楚了:圖上是紅色的 Macro-task Queue 與橘色的 Micro-task Queue 兩個佇列,event listener 與 Timer API 的 callback 進的是 **macro-task queue**,Promise 的 callback 進的才是 micro-task queue。這個區分是下一段那題面試題的全部關鍵,讀字幕時要自己修正回來。 另外補一個作者沒說但決定優先序的規則:一次 tick 裡 macro-task 只取「一個」出來執行,而 micro-task queue 是**清空**——只要裡面還有東西就一直執行,期間新加入的 micro-task 也會在同一輪被吃掉。這就是為什麼無限產生 micro-task 會讓畫面完全卡死,而無限的 setTimeout 不會。
「In between the browser will regend and then run the next iteration.」
3. 用 console.log 順序驗證 event loop 2:28–5:12
作者拿最經典的面試題:四個 console.log 混著 Promise 與 setTimeout(0),問印出順序。他先示範「照字面由上到下」的直覺答案 1-2-3-4 是錯的,再用前一段的 event loop 圖逐步推:同步的 1 和 4 先印,Promise 的 callback 進 microtask queue 所以印 2,setTimeout 的 callback 要等下一個 tick 才印 3。正確順序是 1、4、2、3。




推理因為上一段只給了架構圖,所以作者接著刻意先示範錯誤答案(照字面由上往下的 1-2-3-4),再把同一段程式碼放回圖裡逐行推。先錯後對是他的教法:讓你看見直覺失效在哪一步,你才會記得機制。
AI 補充推演的關鍵在於分辨三種時間點,作者講得快,這裡拆開:**建立**(new Promise 的執行器函式是同步跑的,所以 resolve() 當場就發生)、**排隊**(resolve 只是把 .then 的函式丟進 micro-task queue)、**執行**(要等 call stack 清空)。setTimeout(…, 0) 同理:計時器立刻到期,但 callback 是進 macro-task queue,得等下一輪。 所以順序是 1(同步)→ 4(同步)→ 2(micro-task,本輪結束前)→ 3(macro-task,下一輪)。字幕在這裡同樣把 setTimeout 的去處誤植成 micro-task queue,畫面 s03_225 才是對的:程式碼被高亮的 setTimeout 進的是 call stack 之後排到下一輪,micro-task queue 裡躺著的是 console.log("2")。 順帶一提,如果把 setTimeout 的 0 改成 1000,答案完全不變——這題考的從來不是延遲長短,而是佇列的優先權。
術語:Synchronous Code
4. Execution context 與 scope chain 5:12–8:38
函式被推進 call stack 時建立的不只是函式本身,而是 execution context(stack frame):函式程式碼、this、arguments、變數對應表(指向 heap 的位址)。接著作者定義 scope:JavaScript 裡一對大括號就是一個 scope,函式、if、for、else 層層巢狀;scope chain 是某個位置能存取到的所有 scope,只能由內往外,父 scope 看不到子 scope 的變數。




推理因為上一段留下「stack frame 裡裝了什麼」,所以作者接著定義 execution context:函式碼、this、arguments、以及變數對應表(指向 heap 的位址)。而「變數對應表包含哪些變數」這個問題沒辦法在原地回答,於是他順勢往下定義 scope 與 scope chain——scope chain 正是那張表的來源。
- CExecution Context:函式碼、this、arguments 與變數對應表的集合,決定一次函式呼叫能看到什麼→ 畫地圖
- CScope Chain:內層 scope 是外層的延伸不是複本,只能由內往外找變數→ 畫地圖
- Rvar 與 let/const 的 scope 差異→ 存+回想
- RStack Frame 裡裝的東西就是 Execution Context→ 存+回想
AI 補充作者說「大括號就是 scope」是好用的直覺,但要補一個前提才精確:這條規則對 let / const 成立,對 var 不成立。var 只認 function scope,所以在 if 或 for 的大括號裡宣告的 var 會漏到整個函式,這正是 ES6 引入 let/const 的原因,也是老程式碼裡迴圈變數出錯的經典來源。 另外把「只能由內往外」翻譯成更實用的說法:內層是外層的**延伸**,不是複本。內層函式讀到的是外層變數當下的值,不是宣告時的快照——這個性質在下一段的 closure 會直接變成主角。 畫面 s04_370 那段投影片有個小筆誤:程式碼寫 const netSales = calculateNetSales(...) 但下一行用的是 salesAfterVAT,這個變數並不存在,照抄會 ReferenceError。看圖時把它當 netSales 就好,不影響 scope 的說明。
5. Closure 與垃圾回收 8:38–11:24
Closure 是函式記住它宣告時 lexical scope 裡的變數,只要函式還活著,那些變數就不能被回收。但現代編譯器只會封閉「真的有被用到」的變數。作者用一段程式碼問哪個變數會被 GC:calculateNetProfit 掛在 DOM 元素上等於永生,它用到的 calculateNetSales 與 taxRateCorporate、以及被間接引用的 taxRateVAT 都留著,只有 taxRateDividends 可被標記回收。GC 演算法週期性掃 heap,只做「軟刪除」——標記為可覆寫,而非真的抹掉。




推理因為上一段已經建立了 scope chain,所以作者接著只要加一句話就定義出 closure:函式記住它宣告時 lexical scope 裡的變數。然後他立刻把理論拉回工程現實——理論上整條 scope chain 都不能回收,實務上編譯器只封閉真正被用到的變數——再用一題「哪個變數會被回收」做驗收,手法跟第 3 段完全一樣。
- CClosure:函式記住它宣告時 lexical scope 裡真正用到的變數,不是整條 scope chain→ 畫地圖
- CGarbage Collection:從永生的根往外追引用鏈,追得到的都保留,追不到的才可回收→ 畫地圖
- Amark-and-sweep 回收就像資料庫的 soft delete:標記,不是真的立刻抹掉→ 批判類比
- E追蹤範例:DOM 上的 onclick 抓著整條函式鏈,只有沒人引用的變數能回收→ 存+演練
- P用永生根往外追的方法,檢查自己專案有沒有忘記清掉的監聽器→ 練習
AI 補充作者的追蹤路徑值得原樣記下來,因為它就是判斷記憶體洩漏的標準做法:從**永生的根**開始往外追。這裡的根是 DOM 元素上的 onclick,只要分頁不關就活著 → 它抓著 calculateNetProfit → 這個函式用到 taxRateCorporate 與 calculateNetSales → calculateNetSales 又用到 taxRateVAT,於是這四個都動不了;沒有任何人引用的 taxRateDividends 才是唯一可回收的。 把它反過來用就是實務上最常見的洩漏原因:忘了 removeEventListener 的 listener、沒有 clearInterval 的計時器、閉包裡不小心抓著一整個大物件——它們都是「一個永生的根抓著一條你沒注意到的鏈」。 「soft delete」是作者自創的比喻,不是正式術語;正式的說法是 mark-and-sweep:標記所有從根可達的物件,其餘視為垃圾、其空間可被重新配置。他強調「不是真的抹掉」是對的,但要小心別推論成「記憶體不會真的還給系統」——引擎確實會在整理(compaction)後把大塊空閒記憶體還給作業系統。
術語:Garbage Collection
「And so the only variable that can be garbage collected is tax rate dividends.」
6. 從 callback hell 到 Promise 11:24–15:33
作者回溯歷史:沒有 Promise 之前只能用 callback,把一段程式碼交給對方「之後幫我執行」,舊版 fetch/XMLHttpRequest 就是這樣,並形成 error-first 慣例(第一個參數是錯誤,第二個才是資料)。callback 的問題是層層巢狀難以除錯,也就是 callback hell。Promise 反轉了控制權:不是把 callback 交出去,而是拿回一個帶內部狀態(pending → fulfilled / rejected)的物件,自己往上掛 .then / .catch,而這些 callback 一樣是被推進 microtask queue,在當前 tick 結束前執行。





推理因為要說明 Promise 解決了什麼,所以作者先重建問題本身:callback 時代你只能把一段程式碼交出去、祈禱對方在對的時機呼叫它,巢狀一深就是無法閱讀也無法除錯的 callback hell。有了這個對照組,Promise 的價值才不是「比較新的寫法」,而是控制權的反轉——不是把 callback 交出去,而是拿回一個物件,自己往上掛。
- CInversion of Control:Promise 把「何時執行、執行幾次」的控制權從對方手上拿回來→ 畫地圖
- RPromise 的三種狀態→ 存+回想
- Rerror-first callback 慣例→ 存+回想
AI 補充「inversion of control」這個詞作者只輕輕帶過,但它是這一段真正的重點。callback 模式下,你的程式碼**何時執行、執行幾次、拿到什麼參數**全由對方決定;換成 Promise,狀態機在你手上,而規格保證它只會決議一次、callback 只會被呼叫一次、而且必定是非同步的。callback hell 難以除錯的根因也在這裡:錯誤沒有統一的傳遞路徑,每一層都得自己寫 if (err),漏一層就靜默失敗;Promise 則讓 rejection 沿著鏈往下傳,一個 .catch 收尾。 把它接回第 2 段那張圖就閉合了:promise 被 resolve 時,它掛著的 callback 不是立刻執行,而是被推進 micro-task queue,等這一輪 call stack 清空後才跑。這正是第 3 段那題「2 為什麼排在 4 後面卻在 3 前面」的機制來源。 error-first 慣例(第一個參數是錯誤、第二個才是資料)今天仍然活著——Node.js 的傳統 API 幾乎都是這個形狀,util.promisify 之所以能通用轉換,就是因為它假設你遵守這個約定。
術語:Error-first CallbackPromise States
「So the old implementation of our fetch API was not returning a promise like it does today, but rather asking you to pass a call back to it.」
7. async/await 是 Promise 的語法糖 15:33–16:33
async/await 讓你不用顯式建立 Promise 就能用它,底層會被翻譯回 Promise(作者稱之為 syntax sugar / polyfill)。同一段 promise chain 只要把函式標成 async、在每個 promise 前加 await,就能寫成近乎同步的形狀。程式碼規模越大,從 callback hell → promise chain → async/await 的可讀性差距越明顯。

推理因為上一段已經確立 Promise 是底層機制,所以作者接著把 async/await 定位成「不用顯式建立 Promise 就能用 Promise 的寫法」,並強調它會被翻譯回 Promise——這一步很關鍵,因為它讓讀者知道前面學的 event loop 與 micro-task 規則完全不需要重學。
- Casync/await:await 右邊包成 Promise,函式在此暫停,後續本體排進 micro-task queue→ 畫地圖
- Aasync/await 只是長得像同步,底層還是 Promise 排隊,容易被誤當真的同步→ 批判類比
- E三個各 1 秒的請求依序 await 要 3 秒,改用 Promise.all 只要 1 秒→ 存+演練
AI 補充「翻譯回 Promise」具體是什麼意思,值得講明白:await 右邊的東西會先被包成 Promise,然後**函式在這裡暫停、把控制權交回 call stack**,等這個 promise 決議後,剩下的函式本體被當成一個 micro-task 排隊執行。所以 await 之後的程式碼永遠在下一個 micro-task 才跑——這就是為什麼 async 函式裡 await 前的部分是同步執行的。 實務上最常被漏掉的一點:把多個彼此無關的 await 一行行排下來,等於強迫它們序列化。三個各 1 秒的請求寫成三行 await 要 3 秒,改成 await Promise.all([...]) 只要 1 秒。async/await 讓程式碼看起來像同步,也讓人不小心真的寫成同步。
「And we also call that polyfill or syntax sugar.」
8. Generator:async/await 的另一半 16:33–19:22
async 由兩樣東西組成:一個 Promise,加上一個 generator function。Generator 是「有狀態、可暫停」的函式:加上 star 後呼叫它只會建立 iterator 物件而不執行,每次 next() 才往前跑到下一個 yield 就回傳並暫停,下次從斷點續跑。作者用無限 counter 示範 1、2、3 一路數下去,也提到可以讓它 done。最後把兩半接起來:async 函式裡的每個 await 大致對應底層 generator 的一個 yield,外層 promise 遞迴呼叫 generator,所有 yield 跑完後 promise 才 resolve。




推理因為上一段說 async 是 Promise 加上可暫停的函式,所以作者接著用最小的例子(一個無限 counter)把 generator 的三個關鍵行為演一遍:呼叫它只拿到 iterator 不執行、每次 next() 跑到 yield 就回傳並暫停、下次從斷點續跑。演完再把它接回 async/await,整支影片的鏈就閉合了。
- CGenerator Function:呼叫只拿 iterator,next() 跑到 yield 回傳並凍結,下次從原位置續跑→ 畫地圖
- Agenerator 保存整個執行位置與呼叫狀態,closure 只保存用到的變數→ 批判類比
- Ryield 與 return 在 generator 裡的差別→ 存+回想
AI 補充把 generator 跟第 5 段的 closure 對照著看,這個概念會突然變得平凡:closure 讓函式記住外層的變數,generator 更進一步讓函式記住**自己執行到哪一行**。它保存的不只是資料,是整個 execution context——所以第二次 next() 時,count 還在、while 迴圈的位置也還在。 yield 跟 return 的差別要講清楚:yield 回傳值並凍結,return 回傳值並結束(此後 done 為 true)。作者提到「可以讓它停」指的就是加一個 if 之後改用 return。 最後那張 s08_1140 的對照圖是全片的收束:把 async 函式裡的每個 await 換成 yield,外面套一層驅動器不斷呼叫 next()、拿到 promise 就等它決議再把結果餵回去,就得到等價的行為。真正的實作比這複雜(要處理 throw、return、以及 thenable),但心智模型正是如此——這也解釋了 async/await 為何能在 ES2017 之前用 Babel + regenerator 在舊瀏覽器上跑。 順帶修正一個時間感:generator 是 ES6(2015)的功能,跟 Promise 同期,並不算新。
「Now generator functions are a relatively new feature of the language」
9. 結語與下一步 19:22–20:11
作者說明這支只是粗略帶過底層 polyfill,並預告第二部。他強調光懂基礎還不夠:資深工程師還被期待能推理前端架構,因此建議接著看前端架構與「15 個前端概念」兩支影片,把個別語言概念放回整體系統的脈絡。
推理因為前八段已經把語言層的執行模型建立完整,所以作者最後刻意把層級往上拉:他點出「懂基礎還不夠,資深工程師還被期待能推理前端架構」,並指向自己另外兩支影片,等於承認這支片只涵蓋了資深能力的一個維度。
AI 補充作者在片中兩次說「這支只是粗略帶過、想看手刻 polyfill 就留言」,這其實反映了一個真實的分界:面試會考的是**能不能用模型解釋現象**(第 3、5 段那種題型),而不是能不能背出規格細節。所以複習這支片的正確方式不是重看,而是找幾段自己專案裡的非同步程式碼,用第 2 段那張圖逐行推一次輸出順序——推得出來就是真的懂了。 如果要自己補完這條鏈,最欠缺的一塊是 this 與原型鏈:它跟 execution context 是同一個機制的兩面(第 4 段提過 execution context 裡有 this 的值),卻沒有在這支片裡展開。
4. 總結
這支影片的九個概念不是並列的清單,而是一條首尾相接的鏈。作者先問「JavaScript 到底是誰在執行、憑什麼決定順序」,答案是 event loop——call stack 跑同步碼,macro-task queue 與 micro-task queue 分別排隊,每一輪清空 call stack 後先清 micro-task 才可能 render。這張圖立刻被拿去驗收:一題混著 Promise 與 setTimeout(0) 的 console.log 順序題,正確答案 1、4、2、3 完全由佇列優先權決定。推演過程中函式被當成黑盒推進 call stack,於是往下鑽一層變成 execution context(函式碼、this、arguments、變數對應表),而變數對應表的來源是 scope chain——大括號開 scope、查找只能由內往外。scope chain 決定函式記得哪些變數,這一句話就是 closure 的定義;反過來看同一件事就是 garbage collection:從永生的根(掛在 DOM 上的 handler)往外追引用鏈,追不到的才可回收。講完記憶體後鏡頭拉回那張圖上還沒解釋的 micro-task queue——是誰把東西放進去?答案要從 callback 時代講起:把程式碼交出去、祈禱對方在對的時機呼叫,巢狀一深就是 callback hell;Promise 反轉控制權,讓你拿回一個有 pending/fulfilled/rejected 狀態的物件,而它決議時推進的正是 micro-task queue,圖於是閉合。最後兩層是語法收束:async/await 是 Promise 的語法糖,而它的底層是 Promise 加上一個能暫停的函式,也就是 generator——每個 await 大致對應一個 yield。整條鏈的價值在於:這些概念之所以是「基礎」,不是因為常考,而是因為它們彼此互為前提,缺一個就解釋不了下一個。
勘誤總整理
確定錯誤/已過時見仁見智(取決於版本或情境)
| 段落 | 原話(transcript 逐字) | 說明 |
|---|---|---|
| 7. async/await 是 Promise 的語法糖 15:45 |
「And we also call that polyfill or syntax sugar.」 | async/await 是語言原生語法,是 syntax sugar,但不是 polyfill。polyfill 指的是在缺少某功能的舊環境裡、用既有語法自己實作出該 API;把新語法轉成舊語法的工具叫 transpiler(Babel)。他在第 8 段展示的 asyncPolyfill 示意圖是「用 generator 模擬 async」的教學範例,那才勉強算 polyfill,但語言內建的 async/await 本身不是。 依據: MDN Glossary: Polyfill(以 JavaScript 實作瀏覽器原生未提供的功能)vs. Syntactic sugar;async/await 為 ES2017 原生語法 |
| 2. Event loop 的整體架構 2:13 |
「In between the browser will regend and then run the next iteration.」 | 把「每一輪之間瀏覽器都會 render」講得像必然。實際上 render 是由瀏覽器依畫面更新頻率(一般約 60fps)決定的,多個 tick 可能共用一次 render,頁面在背景分頁時甚至幾乎不 render。正確的說法是「render 只會發生在 tick 之間,但不保證每個 tick 都發生」。 依據: HTML Living Standard, event loop processing model §8.1.7「update the rendering」步驟;瀏覽器只在該次迭代被判定為 rendering opportunity 時才更新畫面 |
| 5. Closure 與垃圾回收 10:13 |
「And so the only variable that can be garbage collected is tax rate dividends.」 | 在他的例子裡 taxRateDividends 是**最上層的 const**,屬於 global/module scope。只要這支 script 的環境還活著(分頁沒關、模組沒卸載),全域繫結一般不會被回收,實際能不能收掉取決於引擎最佳化與這段程式碼是否被包在函式裡。這個結論在「把整段包進一個函式」的情境下完全正確,直接套到全域變數則過於武斷。 依據: V8 的 mark-and-sweep 以全域物件與模組環境為 GC root;ECMAScript 規格未要求回收仍可達的全域繫結 |
| 6. 從 callback hell 到 Promise 12:03 |
「So the old implementation of our fetch API was not returning a promise like it does today, but rather asking you to pass a call back to it.」 | 瀏覽器的 fetch() 從 2015 年首次登場就是回傳 Promise,從來沒有 callback 版本。callback 時代的網路 API 是 XMLHttpRequest(他投影片上寫的其實是自己包的 function fetch(url, callback))。如果把這句聽成「標準 fetch 曾經是 callback 式的」就會記錯歷史;他要表達的應該是「以前自己包的抓資料函式長這樣」。 依據: Fetch Standard 於 2015 年發布,Chrome 42 / Firefox 39 起支援,介面自始即為 Promise-based |
| 8. Generator:async/await 的另一半 16:35 |
「Now generator functions are a relatively new feature of the language」 | generator 是 ES6(ECMAScript 2015)的功能,跟 Promise、let/const、箭頭函式同一批,到 2025 年已滿十年,所有現代瀏覽器與 Node 都原生支援。說它「相對新」在 2015 年成立,今天比較準確的說法是「相對少用」而不是相對新。 依據: ECMAScript 2015 (ES6) 規格納入 generator function;Chrome 39 / Firefox 26 / Node 4 起支援 |
5. 推薦三個下一步
1. 往下挖深:把 async/await 手刻出來
影片第 8 段只給了「await 大致對應 yield」的示意圖,真正的驅動器(怎麼處理 throw、return、thenable)被作者明說跳過了。自己寫一次 polyfill 是把這條鏈焊死的最快方式。
YouTube 搜尋:async await polyfill generator implementation how async await works under the hood javascript co library generator promise runner
2. 往旁邊對照:this、原型鏈與記憶體洩漏實測
第 4 段提到 execution context 裡有 this 的值卻沒展開,而 this 與原型鏈是同一個機制的另一面;第 5 段的 GC 推理也值得用 DevTools 的 Memory 面板實際驗一次,看看全域 const 到底會不會被回收。
YouTube 搜尋:javascript this binding prototype chain explained chrome devtools memory leak heap snapshot tutorial javascript garbage collection mark and sweep v8
3. 往上應用:把執行模型放進前端架構與效能
作者自己在結尾指出「懂基礎還不夠,資深工程師要能推理架構」。long task 卡住 main thread、INP 指標、Web Worker 拆分運算,全都是這支片的 event loop 圖在真實系統裡的直接後果。
YouTube 搜尋:main thread long tasks INP web performance web workers offload main thread javascript frontend architecture senior engineer system design
- 接著看 ←Top 15 Frontend Interview Questions for 2026 (wa/ Senior Engineer) · 面試題只點到 event loop 與記憶體回收,這站把 call stack、兩個佇列與 GC 的機制整條攤開
- 相關 —Embrace Reactive Programming in React and Vue with RxJS - Fahad Ibnay Heylaal · Promise 只決議一次,Observable 可持續推送:同一個非同步問題的兩種模型
- 相關 —15 Frontend Concepts Every Senior Dev Has Mastered · 作者結尾直接指路:語言層基礎之後,用這站把前端整體概念補齊
- 接著看 →Senior Frontend Interview 2026 🎉 | Javascript 🎯 (Mock) [Most Asked Questions] · closure 與 lexical scope 在這站是被驗收的答案,而不是被解釋的內容
- 接著看 →15 Fullstack Concepts Every Frontend Should Know · fetch 與 async/await 在那站建立,這站所有範例都以它為起點
- 接著看 →How Frontend Engineers can master the Full-stack · 同步/非同步與 Promise 是串流與批次合併的前提
- 接著看 →Core Web Vitals Explained: LCP, INP & CLS · 先懂 event loop 與單執行緒,才看得懂 INP 為何被一段長工作卡住
- 相關 —Reactivity in Vue 3 - How does it work? · closure 與 garbage collection 講完,這站就用它們把 effect 存起來
- 接著看 →JavaScript Visualized - Promise Execution · 九概念站建立了 call stack、event loop、callback,這站把 Promise 拆到欄位層
- 接著看 →JavaScript Visualized - Event Loop, Web APIs, (Micro)task Queue · 九個概念用一節帶過 event loop,這站整支把兩個佇列與搬運規則拆開
- 接著看 →JavaScript Visualized - Closures · 九個概念只點到 closure 與 scope chain,這支把同一組詞拆到記憶體層級
- 接著看 →JavaScript Visualized - Execution Contexts · 那站把 call stack、closure、scope chain 列成清單,這站還原成同一組 execution context 零件
- 相關 —Vapor Mode is the Future of Vue · 都在講「同步重算」與「只改該改的」之間的成本差,一個在語言層一個在框架層