跳至主要内容

課程: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/constvar 的本質差異。
  • 實戰意義:掌握這部分,你就能精確追蹤程式碼的執行流,不再被「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 底層鏈結串列:手寫一個簡易版的 useStateuseEffect,徹底搞懂它們的運作順序。
  • 效能優化思維:不再盲目地使用 useMemouseCallback。我們會從 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 會利用 MessageChannelrequestIdleCallback 的概念(雖然實作上略有不同),在瀏覽器空閒的微小間隙中執行一部分渲染工作,然後把控制權交還給瀏覽器處理使用者輸入。如果你不懂 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 元件的渲染又有什麼驚人的相似之處?讓我們繼續前進。