Skip to content

前端框架的本質

💡 學習指南:這篇文章會回答一個根本問題——前端框架(Vue、React、Svelte 等)到底在做什麼? 如果你只學過 HTML、CSS 和一點 JavaScript,完全沒問題,我們從頭講起。

在開始之前,先確認你知道這兩個基礎概念。如果不確定,可以先看對應章節:

  • HTML:網頁的骨架,定義頁面上有哪些元素(標題、段落、按鈕、圖片……)。參見 HTML 與 CSS 佈局
  • JavaScript:讓網頁「動起來」的程式語言,可以修改頁面內容、回應使用者操作。參見 JavaScript 深度指南

還有一個概念會在後面頻繁出現,這裡先做一個完整的說明。

什麼是 DOM?

DOM 的全稱是 Document Object Model,中文叫「文件物件模型」。

當你在瀏覽器中開啟一個網頁時,瀏覽器做的第一件事就是讀取 HTML 程式碼。讀完之後,瀏覽器不會直接拿 HTML 文字去顯示頁面,而是先把 HTML 程式碼轉換成一棵樹狀結構,存放在記憶體裡。這棵樹就叫 DOM 樹。

樹上的每一個節點(Node)對應 HTML 裡的一個標籤。標籤之間的巢狀關係,在 DOM 樹裡就變成了父節點和子節點的關係。

👇 動手試試看: 把滑鼠移到左邊的 HTML 程式碼上,右邊 DOM 樹中對應的節點會高亮。反過來也一樣。每一行 HTML 標籤都對應 DOM 樹上的一個節點。

HTML → DOM treeHow the browser understands your HTML
HTML code you write
1<html>
2<body>
3<h1>My cart</h1>
4<p>3 products total</p>
5<ul>
6<li>Headphones</li>
7<li>Keyboard</li>
8<li>Mouse</li>
9</ul>
10<button>Checkout</button>
11</body>
12</html>
Browser parses
DOM tree generated by the browser
html
└─body
└─h1"My cart"
└─p"3 products total"
└─ul
└─li"Headphones"
└─li"Keyboard"
└─li"Mouse"
└─button"Checkout"
📄
NodeEvery box in the DOM tree is a node. Each HTML tag, such as <h1> or <p>, maps to one node.
🌳
Parent-child relationshipWhen one tag is nested inside another, the DOM tree represents that as a parent-child relationship.
✏️
DOM operationJavaScript can add, remove, or change nodes in the DOM tree. After a node changes, the browser recalculates layout and paints the page again.
Key concept:The DOM is a tree maintained by the browser in memory. It corresponds to the HTML you wrote. JavaScript changes this DOM tree, and the browser updates the screen from that changed tree.

為什麼要了解 DOM? 因為 JavaScript 修改頁面的方式,就是操作這棵 DOM 樹——增加節點、刪除節點、修改節點的內容。而前端框架做的核心工作,就是幫你自動化這些 DOM 操作。後面我們會反覆提到 DOM,理解它是理解框架原理的基礎。


0. 引言:什麼是「前端框架」?

先解釋「框架」這個詞。在程式設計中,框架(Framework) 是一套已經寫好的程式碼和規則,它規定了你的程式碼應該怎麼組織、怎麼執行。你按照它的方式寫程式碼,它幫你處理大量重複、繁瑣的底層工作。

前端框架,就是專門幫你建構網頁介面的框架。目前最常見的有 Vue、React、Svelte、Angular 這幾個。

那它們到底幫你解決了什麼問題?下面這三張卡片概括了核心邏輯:

Problem
  • When data changes, manual DOM updates are easy to miss.
  • The more complex a page becomes, the more places need synchronization.
  • In team work, scattered DOM operations become hard to maintain.
Root cause
  • The browser does not know the relationship between data and UI.
  • Native DOM APIs are low-level operations, not automatic UI synchronization.
  • Developers are forced to act as manual synchronizers.
Framework solution
  • Build a mapping from data to UI: UI = f(State).
  • Detect data changes automatically with reactivity.
  • Compute minimal DOM updates with virtual DOM or compiler optimization.

接下來我們一步步展開,從最基礎的問題講起。


1. 核心問題:資料變了,介面怎麼辦?

1.1 先搞清楚「資料」和「介面」是什麼

在任何一個網頁應用中,都有兩個東西在同時存在:

  • 資料(Data / State):程式內部儲存的資訊。比如「購物車裡有 3 件商品」、「使用者名稱是張三」、「目前選中了第 2 個分頁」。這些資料存在 JavaScript 的變數裡,使用者看不到它們。
  • 介面(UI):使用者在螢幕上看到的東西。比如頁面上顯示「購物車(3)」、顯示「歡迎,張三」、第 2 個分頁高亮。這些是 HTML 元素呈現出來的視覺效果。

資料和介面之間有對應關係:資料是「3 件商品」,介面上就應該顯示「3」。如果資料變成了「4 件商品」,介面上也應該跟著變成「4」。

問題是:這個「跟著變」的過程,誰來負責?

👇 動手點點看: 點擊「新增商品」按鈕,注意觀察:資料(左邊)已經變了,但介面(右邊)沒有跟著更新——它們之間「斷開」了。再點「同步介面」手動修復。

Data (JavaScript variable)
Product count0
Total price¥0
StatusNormal
✅ Synced
UI (what users see)
Cart0 item(s)
Total price¥0
StatusNormal
Core problem:Without a framework, changing data does not automatically update the UI. You must write code to update the interface yourself. If you forget, users see stale or wrong information.

1.2 為什麼 JavaScript 變數變了,介面不會自動變?

這是零基礎最容易困惑的地方,我們把底層原理一步步講清楚。

在 JavaScript 中,變數就是一塊記憶體空間,用來存放資料。當你執行 count = count + 1 時,JavaScript 引擎做的事情非常簡單:把記憶體中 count 這個位置的值從 3 改成 4。做完這一步就結束了,不會再發生任何事。

而頁面上顯示的內容(比如 <span>3</span> 這個 DOM 節點)存放在另一塊完全不同的記憶體空間裡。JavaScript 引擎在修改變數時,根本不知道頁面上有一個 DOM 節點正在顯示這個變數的值,也沒有任何機制讓它去檢查。

所以本質原因是:JavaScript 的變數和 DOM 節點是兩塊獨立的記憶體,它們之間沒有任何自動聯動機制。 修改變數只改變了變數所在的記憶體,DOM 節點所在的記憶體不會受到任何影響。

javascript
let count = 3

// 頁面上有一個 DOM 節點顯示著 count 的值:
// <span id="counter">3</span>

count = 4
// JavaScript 引擎做了什麼?
//   → 把變數 count 在記憶體中的值從 3 改成 4
//   → 結束。沒了。
// 頁面上 <span> 裡顯示的仍然是 "3"

如果你想讓頁面上的顯示也變成「4」,你必須額外寫程式碼,手動找到那個 DOM 節點,然後修改它的內容:

javascript
count = 4  // 第 1 步:改變數

// 第 2 步:你必須自己寫——找到 DOM 節點,把它的文字改成新值
document.getElementById('counter').textContent = count

如果頁面上有 5 個地方顯示著 count 的值(購物車數量、商品列表、總價、小計、狀態提示),你就需要寫 5 段這樣的程式碼。漏掉任何一段,那個位置顯示的就還是舊值,使用者看到的就是錯誤資訊。

1.3 框架做了什麼?兩步建立自動連接

框架能自動同步,靠的是兩步配合——缺一不可。

第一步:你在模板裡「登記」哪些地方要顯示這個變數

框架的 HTML 模板裡,你用 這樣的語法來標記「這裡要顯示 count 的值」:

html
<!-- Vue 模板 -->
<span>購物車:{{ count }} 件</span>    <!-- 位置 A:我要顯示 count -->
<span>總價:¥{{ count * 99 }}</span>   <!-- 位置 B:我也用了 count -->
<span>{{ count > 5 ? '過多' : '正常' }}</span>  <!-- 位置 C:我也用了 count -->

框架第一次渲染頁面時,會把這個「登記關係」記錄下來:位置 A、B、C 都依賴 count

第二步:框架監視變數,變了就查登記表、自動更新

框架用 JavaScript 內建的 Proxy(代理)把你的變數「包裹」起來,讓它變成一個「被監視的變數」。當你修改這個變數時,Proxy 會在賦值的同時悄悄多做一件事:通知框架「count 變了」。框架收到通知後,去查第一步的登記表,把 A、B、C 三個位置全部更新。

原生 JS:
  你寫 HTML → <span id="counter">3</span>(和變數無任何連接)
  你改變數 → count = 4 → 結束,介面毫無反應
  你手動補 → document.getElementById('counter').textContent = 4 → 介面才更新

Vue 框架:
  你寫模板 → <span>{{ count }}</span>(框架記住:這裡依賴 count)
  你改變數 → count = 4 → Proxy 攔截 → 通知框架 → 框架查登記表 → 自動更新 A/B/C

這就是為什麼「只有框架才能自動同步」——原生 HTML 裡的 <span> 和 JS 變數之間根本沒有任何連接,框架的模板語法()才是建立這條連接的關鍵。你寫了 ,框架才知道這裡要顯示 count;框架才能在 count 變化時,精準找到這裡並更新它。

👇 動手點點看: 先選「原生 JavaScript」,點「執行」後注意觀察——變數改了但介面紋絲不動,你要一步步手動同步每個位置。再切換到「使用框架」,同樣點「執行」——變數一改,框架自動完成所有步驟,介面立刻跟上。

What happens when a variable changes?Native JavaScript vs framework
Code you write
// Run when the button is clicked
count = count + 1
// You still have to write these lines:
document.getElementById('count')
.textContent = count
document.getElementById('total')
.textContent = count * 99
Execution flow
1
JavaScript changes the variable
count changes from 0 to 1
2
Find DOM nodes
Call document.getElementById() manually
3
Change DOM content
Set .textContent manually
UI result
Cart0 item(s)
Total price¥0
Why is it not automatic?JavaScript variables are not aware of the UI. When you run count = 4, the engine only changes the value in memory. It does not notify anyone, trigger a callback, or check where count is displayed on the page. The UI will not change unless you update the DOM yourself.

1.4 對比:手動同步 vs 自動同步的實際效果

理解了原理之後,我們來看看在一個稍微複雜一點的場景下,手動同步和自動同步的區別有多大。

👇 動手點點看: 左邊是沒有框架時的「手動同步」方式——每個顯示區域你都需要單獨點「同步」按鈕來更新。右邊是有框架時的「自動同步」方式——你只管點「新增商品」,所有顯示區域自動更新。試試在左邊故意不同步某個區域,看看會發生什麼。

Manual sync / jQuery style
🔴Cart countSynced
0 item(s)
📋Product listSynced
(empty)
💰TotalSynced
¥0
⚠️StatusSynced
Normal
Misses:0
VS
Automatic sync / framework style
🔴Cart countSynced
0 item(s)
📋Product listSynced
(empty)
💰TotalSynced
¥0
⚠️StatusSynced
Normal
Misses:0
Core idea:The essential value of frontend frameworks is automatic synchronization: you change data, and the framework updates every UI location that depends on it.

這就是前端框架存在的根本原因:給 JavaScript 變數加上「被修改時自動通知介面更新」的能力,消滅手動同步帶來的錯誤。


2. 框架的核心思想:用資料描述介面

2.1 兩種寫法的區別

理解了「自動同步」的價值之後,我們來看框架具體是怎麼實現的。

在沒有框架的時代(比如使用 jQuery),程式碼是這樣寫的——你一步一步告訴瀏覽器該做什麼:

javascript
// 第 1 步:找到頁面上 id 為 counter 的元素
var element = document.getElementById('counter')
// 第 2 步:把這個元素的文字內容改成新的值
element.textContent = '4'
// 第 3 步:找到另一個元素,也改掉
document.getElementById('total').textContent = '¥396'
// 第 4 步:如果數量大於 5,還要改狀態提示……

這種寫法叫命令式(Imperative)——你在「命令」瀏覽器一步步執行操作。

有了框架之後,程式碼變成這樣——你只描述「介面應該長什麼樣」:

html
<!-- 我不管這個值怎麼更新到頁面上的 -->
<!-- 我只說:這裡應該顯示 count 的值 -->
<span>{{ count }}</span>
<span>總價:¥{{ count * 99 }}</span>
<span v-if="count > 5">商品過多!</span>

這種寫法叫宣告式(Declarative)——你在「宣告」介面的最終狀態,至於怎麼達到這個狀態,框架自己處理。

2.2 核心公式:UI = f(State)

所有現代前端框架——不管是 Vue、React 還是 Svelte——都遵循同一個核心思想,可以用一個公式來表達:

UI = f(State)

這個公式的意思是:

  • State(狀態):你的應用資料。就是 JavaScript 裡的那些變數:購物車裡有幾件商品、使用者有沒有登入、目前頁面是哪個……
  • f(函式):框架的渲染機制。它知道怎麼把資料變成介面。
  • UI(介面):使用者在螢幕上看到的最終結果。

含義:給定一組資料(State),經過框架的處理(f),就能確定性地得到對應的介面(UI)。資料變了,介面就跟著變。開發者只需要關心資料,不需要關心介面怎麼更新。

👇 動手點點看: 在左邊修改資料(State),觀察右邊的介面(UI)如何自動跟著變化。這就是 UI = f(State) 的直觀體現。

State (data)
→ f →
UI
Change State
2
Rendered result (UI)
Hello, guest!
Cart: 2 item(s)
Total: ¥198
Current theme: Light
Current State snapshot
{ "username": "(empty)", "count": 2, "darkMode": false }
Core idea:You only change State. The framework renders the corresponding UI automatically. The same data always renders the same UI: UI = f(State).

2.3 為什麼宣告式比命令式好?

宣告式寫法的優勢在於:

對比維度命令式(沒有框架)宣告式(有框架)
程式碼量每個更新都要寫具體操作程式碼只寫一次模板,框架自動處理
出錯機率容易漏更新某個地方框架保證所有地方都更新
可讀性程式碼裡混雜著大量 DOM 操作程式碼清晰地描述介面結構
維護成本修改一個功能要改很多地方修改資料邏輯即可,介面自動跟隨

簡單說:宣告式讓你把精力集中在「業務邏輯」(資料怎麼變化)上,不用操心「介面怎麼更新」這個重複且容易出錯的事情。


3. 響應式系統:框架如何知道資料變了?

3.1 什麼是「響應式」?

前面說了「資料變了,介面自動更新」。但這裡有一個技術問題:JavaScript 本身並沒有「變數被修改時自動通知別人」的能力

你寫 count = 4,JavaScript 只是把 count 的值從 3 改成 4,不會自動告訴任何人。框架需要一種機制來「發現」你修改了資料。

響應式(Reactivity) 就是這種機制的總稱:當資料發生變化時,系統能自動感知到變化,並執行相應的更新操作。

3.2 三種不同的實現方式

不同的框架採用了不同的技術方案來實現響應式。這也是 Vue、React、Svelte 之間最根本的區別。

方式一:代理攔截(Vue 的做法)

Vue 使用 JavaScript 內建的 Proxy(代理)機制。Proxy 可以在你讀取或修改一個物件的屬性時,自動執行一段你指定的程式碼。

Vue 把你的資料物件用 Proxy 包裹起來。當你執行 count = 4 時,Proxy 會攔截這次寫入操作,通知 Vue:「count 的值變了」,然後 Vue 去更新所有用到 count 的介面部分。

你作為開發者不需要做任何額外的事情——直接賦值就行,Vue 自動感知。

方式二:顯式呼叫(React 的做法)

React 不使用 Proxy。它要求你必須透過一個專門的函式來修改資料:

javascript
// React 的寫法
const [count, setCount] = useState(0)

// 不能直接寫 count = 4(React 不會感知到)
// 必須呼叫 setCount:
setCount(4)

只有當你呼叫 setCount() 時,React 才知道資料變了,才會去更新介面。如果你直接寫 count = 4,React 完全不知道,介面不會更新。

這種方式更「顯式」——每一次資料變化都是你主動告訴框架的,不會有意外的更新。

方式三:編譯器分析(Svelte 的做法)

Svelte 採用了完全不同的路線。它有一個編譯器(Compiler),在你的程式碼執行之前,編譯器會先分析你的原始碼。

當編譯器看到你寫了 count += 1 這樣的賦值陳述式時,它會自動在這行程式碼後面插入一段「通知介面更新」的程式碼。也就是說,在程式碼執行的時候,「通知」這個動作已經被編譯器提前安排好了。

你的程式碼看起來就是普通的 JavaScript 賦值,但編譯後的程式碼裡多了更新介面的邏輯。

👇 動手點點看: 選擇不同的框架標籤,點擊「修改資料」,觀察每種框架在「引擎蓋下」經歷了哪些步驟來完成資料變化的偵測和介面更新。

count:0
Under the hood
1count = 1 triggers the Proxy set trap
2Notify the dependency tracker: count changed
3Find all components that depend on count
4Update the DOM automatically
Core idea: Vue uses Proxy to intercept reads and writes automatically, so the code feels natural.

3.3 三種方式的對比

對比維度Vue(Proxy 代理)React(顯式呼叫)Svelte(編譯器)
開發者寫法直接賦值 count = 4必須用 setCount(4)直接賦值 count = 4
感知變化的時機執行時自動攔截開發者主動通知編譯時提前插入通知程式碼
執行時效能開銷Proxy 有少量攔截開銷setState 排程有少量開銷幾乎沒有額外開銷
除錯難度中等資料流清晰,較容易需要理解編譯後的程式碼
適合場景追求開發效率和自然寫法追求可預測的資料流追求極致執行效能

三種方式沒有絕對的好壞。Vue 寫起來最自然,React 的資料流最可控,Svelte 的執行效能最好。選擇哪個取決於專案的具體需求。


4. 元件:把介面拆成可複用的小塊

4.1 為什麼要拆?

一個完整的網頁可能有導航欄、側邊欄、內容區、搜尋框、使用者頭像、各種按鈕……如果所有程式碼寫在一個檔案裡,這個檔案會變得非常長、非常難維護。

元件(Component) 就是把介面拆分成一個個獨立的小塊,每個小塊管自己的資料、自己的介面、自己的邏輯。

比如一個電商頁面可以拆成這些元件:

  • NavBar 元件:負責頂部導航欄
  • SearchBox 元件:負責搜尋框
  • ProductCard 元件:負責一張商品卡片
  • ShoppingCart 元件:負責購物車

每個元件都是獨立的。ProductCard 不需要知道 NavBar 裡寫了什麼程式碼,它只需要管好自己。

4.2 元件的三個好處

好處一:複用。 一個 ProductCard 元件寫好之後,可以在頁面上用 100 次——每次傳入不同的商品資料,就會渲染出不同的商品卡片。不需要複製貼上 100 份 HTML 程式碼。

好處二:封裝。 元件內部的資料和邏輯是獨立的。修改 SearchBox 元件的程式碼,不會影響到 ProductCard 元件。多人協作時,不同的人可以同時開發不同的元件,互不干擾。

好處三:可維護。 當某個功能出了問題,你可以直接定位到對應的元件去修復,不需要在一個幾千行的大檔案裡翻找。

👇 動手點點看: 點擊左邊的元件名稱,查看它在頁面上對應的區域。注意觀察:同一個 ProductCard 元件被複用了多次,每次顯示不同的資料。

Component breakdownHow one page is split into independent components
Component tree
📱App (root component)
🧭NavBar
🔍SearchBox
🛒CartIcon
📦ProductCard×3
📄Footer
Page preview
🏠 E-commerce site🔍 Search box🛒 Cart (3)
📦
Product 1
¥199
📦
Product 2
¥298
📦
Product 3
¥397
📦 ProductCard
A card for one product. Write the code once, pass in different product data, and reuse it many times.
Independent dataIsolated stylesReused 3 times
Core idea:Componentization means splitting a large page into independent small pieces. Each component owns its own data, UI, and styles. The same component can be reused in multiple places with different input data.

4.3 元件在程式碼裡長什麼樣?

以 Vue 為例,一個元件就是一個 .vue 檔案,裡面包含三部分:

html
<!-- ProductCard.vue -->
<template>
  <!-- 這裡寫 HTML 結構 —— 元件的「外觀」 -->
  <div class="card">
    <h3>{{ name }}</h3>
    <p>價格:¥{{ price }}</p>
    <button @click="addToCart">加入購物車</button>
  </div>
</template>

<script setup>
// 這裡寫 JavaScript 邏輯 —— 元件的「行為」
const props = defineProps(['name', 'price'])

function addToCart() {
  // 處理「加入購物車」的邏輯
}
</script>

<style scoped>
/* 這裡寫 CSS 樣式 —— 元件的「樣式」 */
.card {
  border: 1px solid #ccc;
  padding: 16px;
}
</style>

使用這個元件時,就像使用一個自訂的 HTML 標籤:

html
<!-- 在其他地方使用 ProductCard 元件 -->
<ProductCard name="無線耳機" price="299" />
<ProductCard name="機械鍵盤" price="599" />
<ProductCard name="顯示器" price="1999" />

三行程式碼就渲染出了三張不同的商品卡片。


5. DOM 操作的代價:為什麼框架要費這麼大力氣?

5.1 什麼是 DOM 操作?

前面提到過 DOM——瀏覽器把 HTML 解析後生成的樹狀結構。DOM 操作就是用 JavaScript 去修改這棵樹上的節點。比如改一段文字、增加一個元素、刪除一個元素、修改一個樣式。

這些操作本身不複雜,但是瀏覽器在執行 DOM 操作之後,需要做很多額外的工作才能讓螢幕上的顯示更新:

  1. 重新計算樣式:這個節點以及它的子節點的 CSS 樣式是否需要變化?
  2. 重新佈局(Layout / Reflow):頁面上所有元素的位置和大小需要重新計算。因為一個元素的改變可能影響到其他元素的位置。
  3. 重新繪製(Paint):把計算好的內容畫到螢幕上。

這三個步驟每一個都有計算成本。如果你的程式碼頻繁觸發 DOM 操作,瀏覽器就會反覆執行這些步驟,頁面就會變卡。

👇 動手點點看: 觀察直接操作 DOM 和批次操作 DOM 的耗時對比。當修改次數增多時,「逐個操作」的耗時會急劇上升。

DOM operation cost comparisonOne-by-one operations vs batch operation
Update DOM one by one
Each data change immediately touches the real DOM, so the browser repeatedly lays out and paints.
Simulated time
1Change → layout → paint
2Change → layout → paint
3Change → layout → paint
4Change → layout → paint
... repeat 16 more times ...
Batch first, update once
All changes are computed in memory first, then committed to the real DOM once.
Simulated time
1Compute 20 changes in memory
2Commit once → layout → paint
Core idea:The real cost of DOM operations is not changing a value itself, but the layout and paint work the browser must do afterward. Reducing DOM writes reduces these expensive calculations.

5.2 框架怎麼解決這個問題?

既然直接操作 DOM 很昂貴,框架就想辦法減少 DOM 操作的次數。具體有兩種策略:

策略一:虛擬 DOM + 差異比較(Vue、React 的做法)

虛擬 DOM(Virtual DOM)是一個 JavaScript 物件,它的結構和真實 DOM 樹一一對應,但它只存在於記憶體中,不會觸發瀏覽器的佈局和繪製。

當資料變化時,框架的處理流程是:

  1. 用 JavaScript 物件建立一棵「新的虛擬 DOM 樹」,描述資料變化後介面應該長什麼樣
  2. 把這棵新樹和舊樹做對比(這個過程叫 Diff,即差異比較),找出哪些節點發生了變化
  3. 只把真正變化的部分應用到真實 DOM 上(這個過程叫 Patch,即打補丁)

這樣一來,不管資料怎麼變化,最終對真實 DOM 的操作總是最少的。

👇 動手點點看: 點擊「修改資料」,觀察虛擬 DOM 如何對比新舊兩棵樹,找出變化的節點。注意看最右邊的「真實 DOM」——只有真正變化的部分才會閃爍。

Virtual DOM diff processThe core mechanism for minimizing DOM updates
Old VTree
div.app
h1: Todo list
ul.list
li: Learn Vue
li: Do homework
li: Play games
Diff Result
div.app
h1: Todo list
ul.list
li: Learn Vue
li: Do homework
li: Play games
Real DOM
div.app
h1: Todo list
  • Learn Vue
  • Do homework
  • Play games
7
Total virtual DOM nodes
0
Real DOM updates needed
Saved DOM operations
Core idea: The virtual DOM compares old and new trees in memory, finds the minimal difference, and updates only the necessary real DOM nodes.

策略二:編譯時精確定位(Svelte 的做法)

Svelte 不使用虛擬 DOM。它的編譯器在你寫程式碼時就分析好了:「當 count 變化時,需要更新第 3 行的 <span> 元素」。執行時直接定位到那個元素去更新,完全不需要對比新舊樹。

這種做法跳過了 Diff 步驟,理論上效能更好。但它依賴編譯器的分析能力——編譯器需要足夠聰明才能正確識別出所有需要更新的地方。


6. 執行時 vs 編譯時:框架設計的核心權衡

6.1 兩個階段

前端程式碼從你寫下到最終在瀏覽器裡執行,會經過兩個階段:

  • 編譯時(Compile-time / Build-time):你的原始碼被建構工具(如 Vite、Webpack)處理,轉換成瀏覽器能直接執行的程式碼。這個過程發生在你的電腦上,在使用者開啟網頁之前。
  • 執行時(Runtime):轉換後的程式碼在使用者的瀏覽器中執行。框架的核心邏輯(比如虛擬 DOM 的 Diff、響應式的追蹤)就在這個階段工作。

6.2 框架在這兩個階段的工作分配

不同框架在這兩個階段分配的工作量不同,這決定了它們的效能特徵和套件體積:

  • React:大部分工作在執行時完成。虛擬 DOM 的建立、Diff、Patch 都發生在瀏覽器中。好處是靈活性高;代價是需要把整個框架的執行時程式碼(約 40KB)傳送給瀏覽器。
  • Vue:混合方式。模板在編譯時被最佳化(編譯器標記出哪些節點是靜態的、不會變化的),但最終的介面更新仍然透過執行時的虛擬 DOM 完成。執行時程式碼約 30KB。
  • Svelte:大部分工作在編譯時完成。編譯器分析你的程式碼,直接生成精確的 DOM 更新指令。執行時幾乎沒有框架程式碼——最終打包出來只有你自己的業務程式碼。套件體積最小。

👇 動手點點看: 點擊不同的框架標籤,查看它們在「執行時 ↔ 編譯時」光譜上的位置,以及各自在打包體積、執行效能、開發體驗上的權衡。

Framework spectrumRuntime ↔ compile time
More runtimeMore compile time
ReactVue 3Vue VaporSvelteSolid.js
💚Vue 3
Hybrid: compiled templates + runtime virtual DOM
Runtime work
60%
Compile-time work
40%
Bundle sizeMediumDeveloper experience★★★★★
Trend: The trend is clear: frameworks keep moving work from runtime to compile time to improve both developer experience and runtime performance.

6.3 行業趨勢

近幾年框架的發展方向很明確:把越來越多的工作從執行時移到編譯時。因為編譯時的計算不佔用使用者的裝置資源,不影響頁面載入速度。

  • Vue 正在開發 Vapor Mode(蒸汽模式),可以跳過虛擬 DOM,在編譯時直接生成 DOM 操作程式碼
  • React 推出了 React Compiler,在編譯時自動最佳化元件的重渲染行為
  • Svelte 5 引入了 Runes 系統,進一步增強編譯時的分析能力

7. 總結

回顧這篇文章的核心要點:

前端框架解決的根本問題:當應用中的資料發生變化時,自動、高效、可靠地更新介面,不需要開發者手動操作 DOM。

它們共同遵循的核心思想:UI = f(State)——介面是資料的函式,開發者只需關注資料的變化,框架負責把資料的變化反映到介面上。

它們的關鍵技術差異

技術點含義
響應式系統框架如何偵測資料變化。Vue 用 Proxy 攔截、React 用顯式 setState、Svelte 用編譯器分析。
虛擬 DOMVue 和 React 用一個 JavaScript 物件來模擬 DOM 樹,透過對比新舊兩棵樹(Diff)來找出最小更新量,減少真實 DOM 操作。
元件化把介面拆成獨立的、可複用的小塊,每個元件管理自己的資料和介面。
編譯時最佳化在程式碼建構階段提前做分析和最佳化,減少執行時的計算量。Svelte 在這方面走得最遠。

一句話:前端框架的本質工作就是——接管「資料到介面」的同步過程,讓開發者只需要思考資料邏輯,不再需要手動操作介面。


名詞對照表

英文術語中文對照解釋
Framework框架一套預先編寫好的程式碼和規則,為開發者提供應用的基礎結構和常用功能。
DOM文件物件模型瀏覽器把 HTML 解析後生成的樹狀資料結構,JavaScript 透過操作它來修改頁面。
Virtual DOM虛擬 DOM用 JavaScript 物件模擬 DOM 樹,透過 Diff 算法找出最小更新路徑,減少真實 DOM 操作次數。
State狀態應用中的資料,比如使用者資訊、購物車內容、頁面目前狀態等。
Reactivity響應式當資料變化時,系統能自動感知並執行對應的介面更新操作。
Proxy代理JavaScript 內建機制,可以攔截對一個物件的讀取和寫入操作。Vue 3 用它來實現響應式。
Component元件一段獨立的、可複用的介面程式碼,包含自己的 HTML 結構、JavaScript 邏輯和 CSS 樣式。
Declarative宣告式一種程式設計方式:你描述「最終想要什麼結果」,由框架來決定怎麼實現。
Imperative命令式一種程式設計方式:你一步一步告訴程式「具體怎麼做」。
Diff差異比較對比新舊兩棵虛擬 DOM 樹,找出哪些節點發生了變化。
Patch打補丁把 Diff 找到的變化部分,應用到真實 DOM 上。
Compile-time編譯時程式碼在建構階段被處理的時期,發生在使用者開啟網頁之前。
Runtime執行時程式碼在使用者瀏覽器中執行的時期。
Compiler編譯器一個程式,把原始碼轉換成另一種形式的程式碼。Svelte 的編譯器把 .svelte 檔案轉換成高效的 JavaScript。