Vue 3.5 的響應式系統是基於 ES6 Proxy ,它讓框架能「攔截物件操作 → 收集依賴 / 觸發更新」,但「Proxy 到底是什麼、怎麼運作」卻很抽象。
今天要來理解 Proxy 的概念,就能更清楚為什麼 ref
/ reactive
能自動更新 UI。
Proxy 是一種設計模式,用來建立一個對原始物件的代理。它提供了一種對其進行控制的方式,通常用於當需要對物件進行額外的處理時,Proxy 可以攔截對原始物件的請求,並在執行這些請求之前或之後執行附加的邏輯。
Proxy 的主要功能包括:
JavaScript 中的 Proxy 是一種對內建物件進行代理的語法,允許開發者攔截對物件屬性讀取、寫入、函式呼叫等操作。
以下範例說明,Proxy 攔截了對 message 屬性的訪問和修改,並附加了一些額外的邏輯來記錄操作。
const target = {
message: "Hello, Proxy!"
};
const handler = {
get: function(target, prop, receiver) {
console.log(Getting property "${prop}");
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value) {
console.log(Setting property "${prop}" to "${value}");
target[prop] = value;
return true;
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message); // 輸出: Getting property "message" \n Hello, Proxy!
proxy.message = "Hello, World!"; // 輸出: Setting property "message" to "Hello, World!"
console.log(proxy.message); // 輸出: Getting property "message" \n Hello, World!
簡單來說,Proxy 就像一個中間人(攔截器)。
當你對某個物件做「存取 / 設定」動作時,不是直接操作物件,而是經過這個代理(Proxy),代理可以:
get
→ 取值時的動作set
→ 設值時的動作這讓 Vue 可以「監控資料變化,並在變化時觸發 UI 更新」。
使用者程式碼
↓
┌──────────────────┐
│ Proxy 代理人 │ ← 攔截操作 (get / set)
└──────────────────┘
↓
真正的物件
state.count
→ Proxy 攔截 get
→ Vue 知道「這裡被使用了」→ 建立依賴關係state.count = 2
→ Proxy 攔截 set
→ Vue 通知「這裡變了」→ 觸發 DOM 更新// 原始資料
const obj = { count: 0 }
// 建立代理
const proxy = new Proxy(obj, {
get(target, key) {
console.log(`讀取屬性 ${key}:`, target[key]) // => 讀取屬性 count: 0
return target[key]
},
set(target, key, value) {
console.log(`修改屬性 ${key} 為`, value) // => 修改屬性 count 為 5
target[key] = value
// 這裡可以觸發「通知 UI 更新」
return true
}
})
// 測試
console.log(proxy.count) // 觸發 get
proxy.count = 5 // 觸發 set
前面提到這麼多內容,Proxy 在 Vue 3 中為什麼重要呢?
文字或許比較難說明,今天先用表格比對 Vue 2 和 Vue 3 實作差異:
特性 | Vue 2 (Object.defineProperty ) |
Vue 3 (Proxy ) |
---|---|---|
監控屬性 | 只能監控已存在屬性 | 可攔截所有屬性(新增 / 刪除) |
陣列方法 | 部分需要改寫 | 全面支援 |
初始化效能 | 大物件初始化慢 | 用到時才代理,更快 |
深層響應 | 需要遞迴遍歷 | 自動遞迴代理 |
實作上明顯可以看到 Proxy 讓 Vue 3.5 在 效能 與 可維護性 上全面超越 Vue 2。
在 <script lang="ts" setup>
中,我們常用 reactive
和 ref
,怎麼從這裡看出 Proxy 在 Composition API 扮演什麼樣的角色呢?
<script lang="ts" setup>
import { reactive, ref } from 'vue'
const count = ref<number>(0) // 本質:value 被 Proxy 攔截
const state = reactive({ user: 'kuku', age: 25 }) // 本質:整個物件被 Proxy 包裝
function increment() {
count.value++
state.age++
}
</script>
<template>
<p>{{ count }}</p>
<p>{{ state.user }} ({{ state.age }})</p>
<button @click="increment">+1</button>
</template>
從上面的範例觀察 背後發生的事:
count.value
→ Proxy 攔截 get
→ 建立依賴。count.value++
→ Proxy 攔截 set
→ 通知 DOM 更新。state.age++
→ Proxy 攔截 → 同樣觸發更新。get
→ 收集依賴。set
→ 觸發更新。Object.defineProperty
,Proxy 更 完整 / 強大 / 高效。ref
和 reactive
的基石,讓 Composition API 成為可能。