理解es6系列-----【proxy和refection】----未完待續

什麼是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個參數:

  1. trapTarget
  2. key 對象的key,字符或者symbol,重寫的就是這個屬性啦
  3. value 賦予這個屬性的值
  4. 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個參數。

  1. trapTarget
  2. key: 屬性的鍵。一個字符串或者symbol。
  3. 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。這樣做是有原因的,兩者的區別在於:
  1. Object上的方法是高層的,而Refect上的方法是語言底層的。
  2. Refeclt.get/setPrototypeOf()方法其實是把內置的[[GetPrototypeOf]]操作包裝了一層,做了輸入校驗。
  3. Object上的這兩個方法其實也是調用內置的[[GetPrototypeOf]]操作,但在調用之前還幹了些別的,並且檢查了返回值,來決定後續行爲。
  4. 比如說,當傳入的target不是對象時,Refeclt.getPrototypeOf()會拋出錯誤,而Object.getPrototypeOf會強制轉換參數到對象,再繼續操作。有興趣的童鞋不妨傳個數字進去試試~
  5. 如果操作不成功,Reflect.setPrototypeOf()會返回一個布爾值來標識操作是否成功,而如果Object.setPrototypeOf()失敗,則會直接拋錯。前者的返回false 其實就會導致後者的拋錯。

結論

Reflect是跟Object同級的一個js數據類型。一個類。
介紹了基本的api。本質是重寫方法。meta編程。

參考文獻:

  1. understanding es6: proxies and reflections
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章