文章目錄
什麼是proxy和refection
- 通過 new Proxy()可 生成一個proxy來代替目標對象(target object)來使用。它等於目標對象的虛擬化,對於使用了該proxy的方法而言,二者看起來是一樣的。通過proxy可以一窺原來只能由js引擎完成的底層操作。
- reflection API 是以Reflect對象爲代表的的一組方法,爲同級的底層操作提供proxy可以重寫的默認行爲。每個proxy trap都有Reflect方法。
Proxy Trap | Overrides the Behavior Of | Default Behavior |
---|---|---|
get | Reading a property value | Reflect.get() |
set | Writing to a property | Reflect.set() |
has | The in operator | Reflect.has() |
deleteProperty | The delete operator | Reflect.deleteProperty() |
getPrototypeOf | Object.getPrototypeOf() | Reflect.getPrototypeOf() |
setPrototypeOf | Object.setPrototypeOf() | Reflect.setPrototypeOf() |
isExtensible | Object.isExtensible() | Reflect.isExtensible() |
preventExtensions | Object.preventExtensions() | Reflect.preventExtensions() |
getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor() | Reflect.getOwnPropertyDescriptor() |
defineProperty | Object.defineProperty() | Reflect.defineProperty |
ownKeys | Object.keys, Object.getOwnPropertyNames(), Object.getOwnPropertySymbols() | Reflect.ownKey() |
apply | Calling a function | Reflect.apply() |
construct | Calling a function with new | Reflect.construct() |
生成一個新的簡單proxy
- 傳入兩個參數,目標對象和句柄 (target and handler)。
- 句柄(handler),就是一個定義了一個或多個“陷阱”(trap)的對象。
- proxy在做沒有定義陷阱的其他操作時,使用默認的行爲。此時二者的表現是相同的,操作proxy就等於操作target。
let target = {};
let proxy = new Proxy(target, {});
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"
target.name = "target";
console.log(proxy.name); // "target"
console.log(target.name); // "target"
用set陷阱來驗證屬性
set 陷阱接受4個參數:
- trapTarget
- key 對象的key,字符或者symbol,重寫的就是這個屬性啦
- value 賦予這個屬性的值
- receiver 操作在哪個對象上發生,receiver就是哪個對象,通常就是proxy。
let target = {
name: "target"
};
let proxy = new Proxy(target, {
set(trapTarget, key, value, receiver) {
// ignore existing properties so as not to affect them
if (!trapTarget.hasOwnProperty(key)) {
if (isNaN(value)) {
throw new TypeError("Property must be a number.");
}
}
console.table(Reflect)
// add the property
return Reflect.set(trapTarget, key, value, receiver);
}
});
// adding a new property
proxy.count = 1; // 這時trapTarget就是target,key等於count,value 等於1, receiver 是 proxy本身
console.log(proxy.count); // 1
console.log(target.count); // 1
// you can assign to name because it exists on target already
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"
// throws an error
proxy.anotherName = "proxy";
- new Proxy 裏,傳入的第二個參數即爲handler,這裏定義了set方法,對應的內部操作是Reflect.set(),同時也是默認操作。
- set proxy trap 和 Reflect.set() 接收同樣的四個參數
- Reflect.set() 返回一個boolean值標識set操作是否成功,因此,如果set了屬性,trap會返回true,否則返回false。
用get陷阱來驗證對象結構(object shape)
object shape: 一個對象上可用的屬性和方法的集合。
與很多其他語言不通,js奇葩的一點在於,獲取某個不存在的屬性時,不會報錯,而是會返回undefined。在大型項目中,經常由於拼寫錯誤等原因造成這種情況。那麼,如何用Proxy的get方法來避免這一點呢?
使用Object.preventExtensions(), Object.seal(), Object.freeze() 等方法,可以強迫一個對象保持它原有的屬性和方法。現在要使每次試圖獲取對象上不存在的屬性時拋出錯誤。在讀取屬性時,會走proxy。.get()接收3個參數。
- trapTarget
- key: 屬性的鍵。一個字符串或者symbol。
- receiver
比起上面的set,少了一個value。Reflect.get()方法同樣接收這3個參數,並返回屬性的默認值。
let proxy = new Proxy({}, {
get(trapTartet, key, receiver) {
if(!(key in receiver)) {
throw new TypeError(`property ${key} doesn't exist`)
}
return Reflect.get(trapTarget, key, recevier)
}
})
// adding a property still works
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
// nonexistent properties throw an error
console.log(proxy.nme); // 識別出了拼寫錯誤,並throws error
用has陷阱來隱藏屬性
- 使用in操作符會使has陷阱被調用。它接收兩個參數trapTarget和key
- 內部的Refelct.has()接收同樣的2個參數。可以修改其返回的默認值。
let target = {
name: "target",
value: 42
};
let proxy = new Proxy(target, {
has(trapTarget, key) {
if (key === "value") {
return false;
} else {
return Reflect.has(trapTarget, key);
}
}
});
console.log("value" in proxy); // false
console.log("name" in proxy); // true
console.log("toString" in proxy); // true
deleteProperty 來阻止屬性被刪除
- delete操作符移除一個對象上的屬性,並返回一個boolean標識操作是否成功。
- 嚴格模式下,試圖刪除一個nonconfigurable屬性(不可改)會拋出錯誤;非嚴格模式下則返回false。
let target = {
name: 'target',
value: 42
}
Object.defineProperty(target, 'name', { configurable: false})
const res = delete target.name // 如果嚴格模式會拋出錯誤
console.log(res) // false
console.log('name' in target) //true
- delete 操作對應的是deleteProperty 陷阱。它接收2個參數, trapTarget 和 key。
let proxy = new Proxy(target, {
deleteProperty(trapTarget, key) {
if (key === "value") {
return false;
} else {
return Reflect.deleteProperty(trapTarget, key);
}
}
});
getPrototypeOf 和 setPrototypeof
- setPrototypeOf陷阱接收兩個參數, trapTarget 和 proto。如果操作不成功,必須返回false。
- getPrototypeOf陷阱接收一個參數,就是trapTarget。必須返回一個對象或者null。否則會跑錯誤。
- 對改寫這兩個函數的限制保證了js語言中Object方法的一致性。
//通過一直返回null隱藏了target的原型,同時不允許修改其原型
let target = {}
let proxy = new Proxy(target, {
getPrototypeOf(trapTarget) {
return null
}
setPrototyoeof(trapTarget, proto) {
return false
}
})
let targetProto = Object.getPrototypeOf(target)
let proxyProto = Object.getPrototypeOf(proxy)
console.log(targetProto === Object.prototype) //true
console.log(proxyProto === Object.prototype) //false
console.log(proxyProto) //null
//succeeds
Object.setPrototypeOf(target, {})
// throw error
Object.setPrototypeOf(proxy, {})
- 如果要用默認行爲,直接調用Reflect.getPrototypeOf/setPrototypeOf, 而不是調用Object.getPrototypeOf/setPrototypeOf。這樣做是有原因的,兩者的區別在於:
- Object上的方法是高層的,而Refect上的方法是語言底層的。
- Refeclt.get/setPrototypeOf()方法其實是把內置的[[GetPrototypeOf]]操作包裝了一層,做了輸入校驗。
- Object上的這兩個方法其實也是調用內置的[[GetPrototypeOf]]操作,但在調用之前還幹了些別的,並且檢查了返回值,來決定後續行爲。
- 比如說,當傳入的target不是對象時,Refeclt.getPrototypeOf()會拋出錯誤,而Object.getPrototypeOf會強制轉換參數到對象,再繼續操作。有興趣的童鞋不妨傳個數字進去試試~
- 如果操作不成功,Reflect.setPrototypeOf()會返回一個布爾值來標識操作是否成功,而如果Object.setPrototypeOf()失敗,則會直接拋錯。前者的返回false 其實就會導致後者的拋錯。
結論
Reflect是跟Object同級的一個js數據類型。一個類。
介紹了基本的api。本質是重寫方法。meta編程。
參考文獻: