課程:JavaScript 與 React 底層原理 第 1 堂:JS 執行模型基礎
課程學習地圖
當你在開發 React 應用時,是否曾遇過這些讓你困惑的時刻:為什麼在 setTimeout 裡面拿到的 state 永遠是舊的?為什麼 Hooks 不能寫在 if 判斷式裡?為什麼 useEffect 的執行時機總是跟我想的不太一樣?
大多數開發者在面對這些問題時,習慣上網搜尋「解決辦法」,然後貼上代碼,問題解決了,但內心的迷惘依然存在。這是因為我們習慣把 React 當作一個「黑盒子」來使用,卻忽略了 React 的所有行為,本質上都是 JavaScript 底層機制的延伸與應用。如果你不了解引擎是如何運行的,你永遠無法真正掌控這輛賽車。
這門課程的目標,就是帶你拆開這個黑盒子。我們不只是要學會「怎麼寫」React,更要透過深入 JavaScript 的底層原理,理解 React「為什麼」要這樣設計。
為什麼要從 JavaScript 底層開始?
在進入複雜的 React Fiber 架構或 Hooks 實作之前,我們必須先達成一個共識:React 並不是一種新的語言,它只是一個巧妙運用了 JavaScript 特性的函式庫。
許多開發者在學習 React 時感到痛苦,是因為他們試圖在不穩固的地基上蓋摩天大樓。當你對 JavaScript 的「執行環境(Execution Context)」、「閉包(Closure)」或「事件循環(Event Loop)」模糊不清時,React 的宣告式(Declarative)語法就會顯得像是一種魔法。
從「背誦規則」到「理解邏輯」
舉個例子,React 官方文檔告訴我們:Hooks 必須在元件的最頂層呼叫,不能放在迴圈、條件式或巢狀函式中。
- 初階開發者:記住這個規則,違法時被 ESLint 警告就改掉。
- 資深開發者:理解 React 內部是用「鏈結串列(Linked List)」來儲存 Hooks 的狀態。如果呼叫順序因為
if判斷式而改變,React 就會找不到對應的狀態位址。
再舉一個例子,關於 useState 的異步特性。
- 初階開發者:知道
setState之後不能馬上拿到最新的state,所以要用useEffect監聽。 - 資深開發者:理解 React 的「批次更新(Batching)」機制,以及它如何利用 Event Loop 中的 Microtask(微任務)來排定渲染時機,從而優化效能。
本課程的設計邏輯是:每一項 React 的核心特性,都會追溯回一個對應的 JavaScript 原理。當你掌握了底層,React 的規則就不再是需要死記硬背的條款,而是理所當然的邏輯推導。
課程四大階段:從運行機制到 UI 抽象
為了讓你建立系統化的知識體系,我們將學習路徑拆解為四個關鍵階段,每一階段都為下一階段打下堅實的基礎。
第一階段:JavaScript 執行模型 —— 程式碼是如何運行的?
這是所有前端開發的起點。我們會暫時放下 React,專注於 JS 引擎(如 V8)的內部運作。
- 執行環境(EC)與 Call Stack:理解程式碼執行的容器與順序。這能解釋為什麼函數元件每次渲染時,內部的變數都是全新的。
- Scope Chain 與 Hoisting:搞清楚變數在哪裡可以被存取,以及
let/const與var的本質差異。 - 實戰意義:掌握這部分,你就能精確追蹤程式碼的執行流,不再被「undefined」或變數覆蓋問題困擾。
第二階段:記憶體與非同步 —— 狀態是如何保存與調度的?
在理解了基本的執行流程後,我們要處理更複雜的「時間」與「空間」問題。
- Closure(閉包):這是 React 的靈魂。useState、useEffect 的底層全是閉包。我們會深入探討閉包如何捕獲變數,以及如何產生(並解決)惡名昭彰的「過時閉包(Stale Closure)」問題。
- Event Loop(事件循環):這是理解 React Scheduler(調度器)的關鍵。我們會拆解 Macrotask 與 Microtask 的優先級。
- Prototype(原型鏈):雖然我們偏好 Hooks,但理解 JS 的物件繼承機制對於理解 React 的元件設計與資料傳遞哲學至關重要。
第三階段:React 核心架構 —— 原理如何轉化為 UI 更新?
有了強大的 JS 底層知識,我們開始拆解 React 的內部實作。
- Fiber 架構:我們會看到 React 如何將渲染過程拆解成「可中斷」的工作單元。這不再是魔法,而是資料結構(Fiber Node)與循環(Work Loop)的應用。
- Reconciliation(協調演算法):深入 Diffing 演算法,理解
key屬性在記憶體層面是如何幫助 React 識別元件身份的。 - Lane 模型:理解 React 18 如何定義更新的「優先級」,讓使用者互動永遠保持流暢。
第四階段:Hooks 深度實作與效能優化
最後,我們將兩者結合,解決實際開發中的高級難題。
- Hooks 底層鏈結串列:手寫一個簡易版的
useState與useEffect,徹底搞懂它們的運作順序。 - 效能優化思維:不再盲目地使用
useMemo或useCallback。我們會從 JS 的「引用型別(Reference Type)」與「淺比較」出發,分析優化的成本與收益。 - 實戰診斷:學會使用 React DevTools Profiler 找出真正的瓶頸,並能從底層原理解釋為什麼這裡會產生不必要的 re-render。
具體實例:底層原理與 React 的連結
為了讓你更有感,我們先預覽兩個課程中會深入探討的連結點。
例子一:為什麼 useState 離不開閉包?
考慮以下代碼:
const Counter = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setTimeout(() => {
console.log(count); // 這裡為什麼會拿到舊的值?
}, 3000);
};
return <button onClick={handleClick}>點擊 {count}</button>;
};
當你點擊按鈕並在 3 秒內再次快速點擊多次,console.log 印出的 count 可能是你第一次點擊時的值,而不是最新的值。這就是典型的 Stale Closure(過時閉包)。
在 JavaScript 中,handleClick 是一個函數,當它被建立時,它「閉包」捕獲了當時環境中的 count 變數。在 React 中,每次渲染(Render)都是一次獨立的函數呼叫。如果你不理解閉包是如何在記憶體中保留變數參考的,你就無法理解為什麼 React 的狀態更新看似是「快照(Snapshot)」而非即時變動。
例子二:React 調度器與 Event Loop
React 18 引入了併發模式(Concurrent Mode),其中一個核心概念是「時間切片(Time Slicing)」。React 發現如果一次更新太多 DOM,會導致主執行緒(Main Thread)阻塞,造成瀏覽器卡頓。
為了優化這一點,React 實作了一個 Scheduler。這個 Scheduler 必須非常精確地知道:「現在主執行緒有空嗎?」。
這需要深刻理解瀏覽器的渲染幀(Frame)與 Event Loop。React 會利用 MessageChannel 或 requestIdleCallback 的概念(雖然實作上略有不同),在瀏覽器空閒的微小間隙中執行一部分渲染工作,然後把控制權交還給瀏覽器處理使用者輸入。如果你不懂 Event Loop,你永遠無法理解為什麼 startTransition 能夠提升效能。
一段從「會寫」到「精通」的旅程
這門課程不是為了教你如何快速做出一個 Todo List(雖然我們會做很多範例),而是為了把你培養成一名能夠診斷複雜問題、能夠在面試中對答如流、並能寫出高性能代碼的進階工程師。
學習底層原理往往是枯燥且充滿挑戰的。你會遇到很多抽象的概念,像是「執行環境棧(Execution Context Stack)」或是「雙緩衝樹(Double Buffering)」。但請記住,這些知識是你與普通開發者拉開差距的分水嶺。
當你不再害怕看 React 的源碼,當你看到一個奇怪的 bug 時能直覺地聯想到它是哪個 JS 機制在作祟,你就會發現:原本複雜的 React,其實變得非常簡單、透明且優雅。
接下來,我們將正式踏上這段旅程。讓我們從一切的起點 —— 執行環境(Execution Context) 開始。
總結與銜接
在本部分中,我們建立了一個清晰的學習地圖,明確了 JavaScript 底層與 React 設計之間的共生關係。我們理解到,React 的所有高級特性(如 Hooks、Fiber、Concurrent Mode)都是為了在 JavaScript 的限制下,提供更好的開發體驗與使用者效能。
關鍵要點
- 底層驅動架構:React 規則(如 Hooks 限制)是出於 JS 底層資料結構的必然結果。
- 四大階段路徑:從最基礎的 EC(執行環境)出發,經過記憶體管理,最終通向 React 的核心調度機制。
- 直覺建立:理解「閉包」與「引用」是解決 90% React Bug 的關鍵。
現在,我們已經準備好進入微觀世界。在下一個章節中,我們將探討 JavaScript 引擎是如何開始它的第一步:建立執行環境(Execution Context)。當你在瀏覽器輸入一行代碼並按下 Enter 時,JS 引擎內部究竟發生了哪些準備工作?這與 React 元件的渲染又有什麼驚人的相似之處?讓我們繼續前進。