[JS] JavaScript 代理(Proxy)
此篇為各筆記之整理,非原創內容,資料來源可見下方連結:
- 👍 Proxy and Reflect @ javascript.info
- Proxy @ MDN
- JavaScript Proxy @ DWB
- Proxy Pattern @ Frontend Masters
所有對 proxy 做的操作都會「透過 proxy」作用在 target 上,透過 proxy 可以讓開發者在存取到 target 前先做一下操作,也就是攔截(intercept)的概念:
let proxy = new Proxy(target, handler);
// 執行完 proxy 後原本的 targetObj 也會一併改變
const proxy = new Proxy(target, {
get: (target, prop, receiver) => {
return Reflect.get(target, prop);
},
set: (target, prop, value, receiver) => {
return Reflect.set(target, prop, value);
},
});
target
:要被代理的物件,可以是任何東西,包含 functionhandler
:proxy 的設定,可以參考 Proxy and Reflect @ javascript.info 中的整理
❗ 注意:proxy 並不是把 targetObj 複製一份,而是參照到同一個物件上,因此對 proxy 做的操 作會影響到原本的 targetObj。
提示
因為 Reflect 的許多方法在成功時會回傳 true
,失敗時會回傳 false
,因此適合和 Proxy
搭配使用。
設定預設值
const withDefaultValue = (target, defaultValue) =>
new Proxy(target, {
get: (obj, prop) => (prop in obj ? obj[prop] : defaultValue),
});
let coordination = {
x: 4,
y: 19,
};
console.log(coordination.x, coordination.y, coordination.z); // 4, 19, undefined
// set default value with 0
coordination = withDefaultValue(coordination, 0);
console.log(coordination.x, coordination.y, coordination.z); // 4, 19, 0
透過當屬性不存在會回傳預設值的這個特性,也可以做到 i18 dictionary 的功能,例如:
// https://javascript.info/proxy
let dictionary = {
Hello: 'Hola',
Bye: 'Adiós',
};
// 直接把 dictionary 覆蓋成有 proxy 的版本,避免有人直接對 dictionary 的 target 做操作
dictionary = new Proxy(dictionary, {
get: (target, property, receiver) => {
if (property in target) {
// 如果有對應的翻譯就回傳翻譯的內容
return target[property];
} else {
// 否則什麼都不做
return property;
}
},
});
console.log(dictionary.Hello); // Hola
console.log(dictionary.Foo); // Foo
作為資料驗證(Validation)
Proxy 可以用來驗證某一 Object 設定的屬性和值是否合法:
// 把 volume 的值限制在 0 ~ 1 之間
const proxy = new Proxy(
{},
{
set: (obj, prop, value) => {
if (prop === 'volume') {
if (value > 1) {
value = 1;
} else if (value < 0) {
value = 0;
}
}
obj[prop] = value;
return true;
},
},
);
注意
當 set
成功改值後,需要回傳 true
,如果忘了回傳,或是會回傳了 falsy value,則會觸發 TypeError
。雖然目前(2022/10/02)在瀏覽器執行時,即使沒有回傳 true
也不會回報錯誤(在 Node.js 會),因此還是建議要加上回傳值。
作為資料格式化(formatting)
Proxy 可以把送進物件的屬性都先進行格式化的動作:
const form = new Proxy(
{},
{
set: (obj, prop, value) => {
if (prop === 'content') {
obj[prop] = {
...value,
isEditing: false,
hasSubmitted: false,
};
}
},
},
);
form.content = {
label: 'Age',
defaultValue: 20,
};
console.log(form.content);
// {
// label: 'Age',
// defaultValue: 20,
// isEditing: false,
// hasSubmitted: false
// }