Proxy 讓我們可以對任何物件的絕大部分行為進行監聽和干涉,實現更多的自定義程式行為。
用法:new Proxy(target, handler)。
Proxy 通過設定行為監聽方法來捕獲程式對對應物件的行為。
const obj = {};
const proxy = new Proxy(obj, {
// ...
})
Proxy 的構造器接受兩個引數,第一個引數為需要進行包裝的目標物件,第二個引數則為用於監聽目標物件行為的監聽器,其中監聽器可以接受一些引數以監聽相對應的程式行為。
監聽屬性、引數及監聽內容
屬性值 | 監聽器引數 | 監聽內容 |
---|---|---|
has | (target, prop) | 監聽 in 語句的使用 |
get | (target, prop, reciver) | 監聽目標物件的屬性讀取 |
set | (target, prop, value, reciver) | 監聽目標物件的屬性賦值 |
deleteProperty | (target, prop) | 監聽 delete 語句對目標物件的刪除屬性行為 |
ownKeys | (target) | 監聽 Object.getOwnPropertyName() 的讀取 |
apply | (target, thisArg, arguments) | 監聽目標函式(作為目標物件)的呼叫行為 |
construct | (target, arguments, newTarget) | 監聽目標建構函式(作為目標物件)利用 new 而生成例項的行為 |
getPrototypeOf | (target) | 監聽 Objext.getPrototypeOf() 的讀取 |
setPrototypeOf | (target, prototype) | 監聽 Objext.setPrototypeOf() 的呼叫 |
isExtensible | (target) | 監聽 Objext.isExtensible() 的讀取 |
preventExtensions | (target) | 監聽 Objext.preventExtensions() 的讀取 |
getOwnPropertyDescriptor | (target, prop) | 監聽 Objext.getOwnPropertyDescriptor() 的呼叫 |
defineProperty | (target, property, descriptor) | 監聽 Object.defineProperty() 的呼叫 |
has
可以通過為 Proxy 的 handler 定義 has 監聽方法,來監聽程式通過 in 語句來檢查一個字串或數字是否為該 Proxy 的目標物件中某個屬性的屬性鍵的過程。
const p = new Proxy({}, {
has(target, prop){
console.log(`Checking "${prop}" is in the target or not`);
return true;
}
})
console.log(`foo` in p);
// Checking "foo" is in the target or not
// true
該監聽方法有兩個需要注意的地方,如果遇到這兩種情況,便會丟擲 TypeError 錯誤。
1.當目標物件被其他程式通過 Object.preventExtensions() 禁用了屬性擴充 (該物件無法再增加新的屬性,只能對當前已有的屬性進行操作,包括讀取、操作和刪除,但是一旦刪除就無法再定義) 功能,且被檢查的屬性鍵確實存在與目標物件中,該監聽方法便不能返回 false。
const obj = {foo: 1};
Object.preventExtensions(obj);
const p = new Proxy(obj, {
has(target, prop){
console.log(`Checking "${prop}" is in the target or not`);
return false;
}
})
console.log(`foo` in p);
//丟擲Uncaught TypeError:
2.當被檢查的屬性鍵存在與目標物件中,且該屬性的 configurable 配置是 false 時,該監聽方法不能返回 false。
const obj = {};
Object.defineProperty(obj, `foo`, {
configurable: false,
value: 10
})
const p = new Proxy(obj, {
has(target, prop){
console.log(`Checking "${prop}" is in the target or not`);
return false;
}
})
console.log(`foo` in p);
//丟擲Uncaught TypeError:
get
Getter只能對已知的屬性鍵進行監聽,而無法對所有屬性讀取行為進行攔截,而 Proxy 可以通過設定 get 監聽方法,攔截和干涉目標物件的所有屬性讀取行為。
const obj = {foo: 1};
const p = new Proxy(obj, {
get(target, prop){
console.log(`Program is trying to fetch the property "${prop}".`);
return target[prop];
}
})
alert(p.foo); // Program is trying to fetch the property "foo".
alert(p.something); // Program is trying to fetch the property "something".
這個監聽方法也存在需要注意的地方——當目標物件被讀取屬性的 configurable 和 writable 屬性都為 false 時,監聽方法最後返回的值必須與目標物件的原屬性值一致。
const obj = {};
Object.defineProperty(obj, `foo`, {
configurable: false,
value: 10,
writable: false
})
const p = new Proxy(obj, {
get(target, prop){
return 20;
}
})
console.log(p.foo);
set
handler.set 用於監聽目標物件的所有屬性賦值行為。注意,如果目標物件自身的某個屬性是不可寫也不可配置的,那麼 set 不得改變這個屬性的值,只能返回同樣的值,否則報錯。
const obj = {};
const p = new Proxy(obj, {
set(target, prop, value){
console.log(`Setting value "${value}" on the key "${prop}" in the target object`);
target[prop] = value;
return true;
}
})
p.foo = 1;
// Setting value "1" on the key "foo" in the target object
apply
handler.apply , Proxy 也為作為目標物件的函式提供了監聽其呼叫行為的屬性。
const sum = function(...args) {
return args
.map(Number)
.filter(Boolean)
.reduce((a, b) => a + b);
}
const p = new Proxy(sum, {
apply(target, thisArg, args) {
console.log(`Function is being called with arguments [${args.join()}] and context ${thisArg}`);
return target.call(thisArg, ...args);
}
})
console.log(p(1, 2, 3));
// Function is being called with arguments [1,2,3] and context undefined
// 6
construct
handler.construct, Proxy 也可以將類作為目標監聽物件,並監聽其通過 new 語句來生產新例項的行為,這同樣可以使用再作為構造器的建構函式上。
class Foo{};
const p = new Proxy(Foo, {
construct(target, args, newTarget){
return {arguments: args} // 這裡返回的結果會是 new 所得到的例項
}
});
const obj = new p(1, 2, 3);
console.log(obj.arguments); // [1, 2, 3]
建立可解除 Proxy 物件
用法:Proxy.revocable(target, handler) : (proxy, revoke)。
const obj = {foo: 10};
const revocable = Proxy.revocable(obj, {
get(target, prop){
return 20;
}
})
const proxy = revocable.proxy;
console.log(proxy.foo); // 20
revocable.revoke();
console.log(proxy.foo);
// TypeError: Cannot perform `get` on a proxy that has been revoked
Proxy.revocable(target, handler) 會返回一個帶有兩個屬性的物件,其中一個 proxy 便是該函式所生成的可解除 Proxy 物件,而另一個 revoke 則是將剛才的 Proxy 物件解除的解除方法。