Proxy 讓你包裹一個物件,攔截並自訂對它的基本操作(讀取、寫入、刪除、判斷屬性是否存在等)。這類「對程式行為本身進行程式設計」的能力,稱為元程式設計(Metaprogramming)。
Reflect 則是一組與 Proxy 攔截器一一對應的內建方法,用來在攔截器內「執行原本預設的操作」。兩者通常搭配使用。
基本概念#
new Proxy(target, handler) 接收兩個參數:被代理的目標物件,以及定義各種攔截器(trap)的處理器物件。
const target = { name: "Andrew" };
const proxy = new Proxy(target, {
get(obj, prop) {
console.log(`讀取 ${prop}`);
return obj[prop];
},
set(obj, prop, value) {
console.log(`設定 ${prop} = ${value}`);
obj[prop] = value;
return true; // set trap 必須回傳布林表示成功
},
});
proxy.name; // log: 讀取 name → "Andrew"
proxy.age = 30; // log: 設定 age = 30
常見攔截器#
| Trap | 攔截的操作 | 觸發範例 |
|---|---|---|
get | 讀取屬性 | proxy.x |
set | 寫入屬性 | proxy.x = 1 |
has | in 運算子 | 'x' in proxy |
deleteProperty | delete | delete proxy.x |
apply | 函式呼叫 | proxy() |
construct | new 呼叫 | new proxy() |
搭配 Reflect#
在攔截器內若要「先做點事、再執行預設行為」,用 Reflect 比直接操作 obj[prop] 更安全——它能正確處理 this 與回傳值,且方法簽名與 trap 完全對應。
const proxy = new Proxy(target, {
get(obj, prop, receiver) {
console.log(`讀取 ${prop}`);
return Reflect.get(obj, prop, receiver); // 等同預設讀取行為
},
set(obj, prop, value, receiver) {
return Reflect.set(obj, prop, value, receiver); // 回傳布林
},
});原則:攔截器負責「加邏輯」,
Reflect負責「做原本該做的事」。 這樣攔截器只需專注於要插入的行為,預設語義交給Reflect保證正確。
實用範例#
屬性驗證#
const user = new Proxy(
{},
{
set(obj, prop, value) {
if (prop === "age" && (typeof value !== "number" || value < 0)) {
throw new TypeError("age 必須是非負數");
}
return Reflect.set(obj, prop, value);
},
}
);
user.age = 30; // OK
user.age = -5; // TypeError
預設值與防呆讀取#
// 讀取不存在的屬性時回傳預設值,而非 undefined
const withDefault = new Proxy(
{ theme: "dark" },
{
get(obj, prop) {
return prop in obj ? obj[prop] : "(未設定)";
},
}
);
withDefault.theme; // "dark"
withDefault.lang; // "(未設定)"
負索引陣列#
function negativeArray(arr) {
return new Proxy(arr, {
get(obj, prop) {
const idx = Number(prop);
if (idx < 0) return obj[obj.length + idx];
return Reflect.get(obj, prop);
},
});
}
const arr = negativeArray(["a", "b", "c"]);
arr[-1]; // "c"
arr[-2]; // "b"
Proxy vs getter/setter#
在 Proxy 出現前,攔截屬性存取要靠物件的 get/set 存取器,但那只能針對「已知的特定屬性」。Proxy 的強項是能攔截任意屬性,甚至是尚未存在的屬性。
| 比較項 | getter/setter | Proxy |
|---|---|---|
| 攔截範圍 | 預先定義的特定屬性 | 所有屬性,含動態新增的 |
| 攔截操作 | 讀 / 寫 | 讀寫、in、delete、呼叫… |
| 動態屬性 | 做不到 | 天然支援 |
| 效能 | 接近原生 | 明顯較慢 |
Proxy 有顯著的效能開銷。 每次經過攔截器都比直接存取慢上一個量級,且有些操作(
===比較、typeof、instanceof)無法被攔截。在熱路徑(如動畫迴圈、大量資料處理)上使用前,務必實測;許多響應式框架(如 Vue 3)正是用 Proxy 實現響應式,但會搭配快取與批次更新來攤平成本。
小結#
Proxy攔截物件的基本操作,是元程式設計的核心工具。Reflect提供與攔截器對應的「預設行為」方法,搭配使用最穩妥。- 適合做驗證、預設值、日誌、響應式等橫切邏輯。
- 代價是效能與部分操作無法攔截,熱路徑慎用。