理解es6系列-----【proxy和refection】----未完待續
阿新 • • 發佈:2018-12-17
什麼是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程式設計。
參考文獻: