# Day 6:JavaScript 程式碼執行排序:遞迴函數、Call Stack、Task Queue
**Published by:** [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/)
**Published on:** 2025-11-27
**URL:** https://paragraph.com/@gcake/day-6
## Content
Call Stack;遞迴函數與 call stack 的關係;Task Queue(非同步的概念再釐清)Call Stack試著理解遞迴函數運作方式的過程,感覺需要瞭解 JS 如何排序要執行的指令,聯想到 Day1-3 使用 Vitest readline 的時候讀到的非同步和任務排序的概念 查閱文件和科普說明,發現一樣是任務排序但是不同情境的東西: What the heck is the event loop anyway? | Philip Roberts | JSConf EU JavaScript's call stack/event loop/task queue 互動關係的視覺化操作界面: loupe 這個視覺化工具對於理解瀏覽器如何執行被 JavaScript 程式碼定義的行為很有幫助 規劃好程式佇列和任務執行順序,才能有流暢的使用者體驗(不卡頓)JavaScript 一次只能做一件事(一個程式碼片段),瀏覽器有提供 WebAPI ,透過搭配 event loop 的方式同時處理多個任務JavaScript 會在 call stack 中依序堆疊要執行的程式碼片段,越往後被呼叫的 function 被堆疊在越上方,會被優先執行並從堆疊中清除。在堆疊中的單一程式碼片段如果執行時間太久,整個流程會全部卡住,此時瀏覽器無法執行任何動作或反應/渲染,稱為 blocking 阻塞。task queue 會依序排列 WebAPI 處理好的任務,等候排入 call stack 執行。排列順序會按照 Macro/micro task 判斷。event loop 的作用是去監控堆疊(call stack)和工作佇列(task queue),當堆疊已經完全清空、沒有執行項目的時候,便把佇列中的內容拉到堆疊中去執行。參考文件[JS] 理解 JavaScript 中的事件循環、堆疊、佇列和併發模式(Learn event loop, stack, queue, and concurrency mode of JavaScript in depth)What the heck is the event loop anyway?Concurrency model and Event Loop - MDN Web Docs遞迴函數和 Call Stack利用 function 內自我呼叫的方式堆疊 call stack 後再回收,以 MDN 官方文件舉例的程式碼來看:function foo(i) { if (i < 0) { return; } console.log(`begin: ${i}`); foo(i - 1); console.log(`end: ${i}`); } foo(3); 步驟呼叫(目前執行的那一層)call stack(頂端在上)console.log 結果遞迴呼叫動作1foo(3)[foo(3)]
[global]begin: 3呼叫 foo(2)2foo(2)[foo(2)]
[foo(3)]
[global]begin: 2呼叫 foo(1)3foo(1)[foo(1)]
[foo(2)]
[foo(3)]
[global]begin: 1呼叫 foo(0)4foo(0)[foo(0)]
[foo(1)]
[foo(2)]
[foo(3)]
[global]begin: 0呼叫 foo(-1)5foo(-1) 進 if,return[foo(-1)]
[foo(0)]
[foo(1)]
[foo(2)]
[foo(3)]
[global] → foo(-1) 結束後彈出無輸出無(直接 return)6回到 foo(0),執行遞迴後面的程式[foo(0)]
[foo(1)]
[foo(2)]
[foo(3)]
[global]end: 0無(這層遞迴已呼叫過)7foo(0) 執行完,returnfoo(0) 從堆疊彈出 → 剩:
[foo(1)]
[foo(2)]
[foo(3)]
[global]無無8回到 foo(1),執行遞迴後面的程式[foo(1)]
[foo(2)]
[foo(3)]
[global]end: 1無9foo(1) 執行完,returnfoo(1) 彈出 → 剩:
[foo(2)]
[foo(3)]
[global]無無10回到 foo(2),執行遞迴後面的程式[foo(2)]
[foo(3)]
[global]end: 2無11foo(2) 執行完,returnfoo(2) 彈出 → 剩:
[foo(3)]
[global]無無12回到 foo(3),執行遞迴後面的程式[foo(3)]
[global]end: 3無13foo(3) 執行完,returnfoo(3) 彈出 → 剩:
[global]無無最終輸出順序:begin: 3begin: 2begin: 1begin: 0end: 0end: 1end: 2end: 3Task Queue現代說法通常會分成「task(或 macrotask)queue」和「microtask queue」,是兩個不同的佇列。 整個 JavaScript 程式碼執行順序(就是 event loop 主要在做的事)大致是:如果 call stack 不為空,就把目前的任務執行到結束(同步程式碼、或某個已經放進 stack 的 task/microtask)。當 call stack 清空時:先把 microtask queue 清到空(途中新增的 microtask 也要優先處理完)。然後從 task(macrotask)queue 取出最舊的一個任務,放進 call stack 執行。在一輪 task 執行完、microtask queue 清空後,瀏覽器才有機會做一次畫面渲染。目前有練習到的 microtask:async/await (Promise 語法糖)Day1-3 練習 Vitest 有用到 async/await 處理終端機輸入。當程式執行到 await somePromise 時:async function main() { const input = await question('請輸入數字:'); // ... 後面的處理 } 先「暫停」目前這個 async 函式,讓它後面的程式碼不要繼續往下跑。等 somePromise 被 resolve/reject 之後(如使用者輸入數字後),恢復這個 async 函式的後半段執行;這個「恢復動作」會被排進 microtask queue。其他 microtask handler 和原始的 Promise 寫法還沒練習到。 參考文件來源:Promise - JavaScript - MDN Web Docsawait - JavaScript - MDN Web DocsUsing microtasks in JavaScript with queueMicrotask() - MDNEvent loop: microtasks and macrotasks - JavaScript.info簡單 Recap:從遞迴函數開始理解 JavaScript 執行規則起點:想理解遞迴函數怎麼執行(foo(3) → 2 → 1 → 0 → -1,再一層層往回印出結果)。發現:遞迴本身只用到 call stack 的同步行為(先進後出),跟非同步任務排序無關——整段遞迴從頭到尾都在同一個 task 裡一次跑完。延伸:這引導到理解「JS 單執行緒 + event loop」的完整機制:call stack:存放正在執行的函式,一次只能處理一個任務。task queue(macrotask):存放 setTimeout、DOM 事件等非同步任務。microtask queue:存放 Promise handler、async/await 後半段等微任務。event loop 規則:stack 清空後,先清空 microtask queue,再從 task queue 取下一個任務。結論:遞迴 = call stack 堆疊與彈出;非同步 = event loop 在不同佇列間排程。兩者分別對應「單一任務內的執行流程」與「任務之間的排程順序」。這樣整個程式碼執行排序規則大致上就理解了。
## Publication Information
- [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/): Publication homepage
- [All Posts](https://paragraph.com/@gcake/): More posts from this publication
- [RSS Feed](https://api.paragraph.com/blogs/rss/@gcake): Subscribe to updates