【ES6系列】Proxy

一、概述

Proxy 從字面意思可以理解爲 “代理”,用於修改某些操作的默認行爲,等同於在語言層面做出修改。通過代理可以在目標對象之前架設一層 “攔截” ,外界對該對象的訪問都必須通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。

var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
});

上面的這段代碼對空對象進行了一層攔截,重新定義了屬性的讀取和設置行爲。

Proxy 是一個構造函數,用於實例化實例。

let proxy = new Proxy(target,handler);

其中,target 就是攔截的目標,handler 用於控制攔截行爲。具體支持13種攔截行爲:

  • get(target,prop,receiver) —— 攔截對象屬性的讀取
  • set(target, propKey, value, receiver) —— 攔截對象的設置
  • has(target, propKey) —— 攔截 propKey in proxy 的操作
  • deleteProperty(target, propKey) —— 攔截 delete proxy[propKey] 的操作
  • ownKeys(target) —— 攔截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in 循環
  • getOwnPropertyDescriptor(target, propKey) —— 攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
  • defineProperty(target, propKey, propDesc) —— 攔截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs)
  • preventExtensions(target) —— 攔截Object.preventExtensions(proxy)
  • getPrototypeOf(target) —— 攔截 Object.getPrototypeOf(proxy)
  • isExtensible(target) —— 攔截 Object.isExtensible(proxy)
  • setPrototypeOf(target, proto) —— 攔截Object.setPrototypeOf(proxy, proto)
  • apply(target, object, args) —— 攔截 Proxy 實例作爲函數調用的操作,比如 proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args) —— 攔截 Proxy 實例作爲構造函數調用的操作,比如 new proxy(...args)

二、實例方法

1.get()

用於攔截某個屬性的讀取操作,可以接受三個參數,分別是:目標對象、屬性名和 proxy 實例本身。

let person = {name:"jonas"}
let proxy = new Proxy(person,{
    get(target, p, receiver) {
        if(p in target)
            return target[p]
        throw new Error("p is not in target")
    }
})
proxy.name //jonas
proxy.age //拋出錯誤

你會發現通過這段代碼改變了 JS 原生的編程能力。默認情況下訪問一個對象的不存在的屬性,結果是 undefined ,然而上面的代碼訪問了一個不存在的屬性 age ,結果拋出了我們自定義的異常。從而達到了修改編程能力的效果。

除了直接攔截目標對象以外,還可以直接攔截原型對象:

let proxy = new Proxy({},{
    get(target, p, receiver) {
        if(p in target)
            return target[p]
        throw new Error("p is not in target")
    }
})
let obj = Object.create(proxy)
obj.name = "jonas"
console.log(obj.name) //jonas
console.log(obj.age) //報錯

實現數組負數索引:

let arr = [1,2,3,4,5]
let proxy = new Proxy(arr,{
    get(target, p, receiver) {
        let index = Number(p)
        if(index >= 0 && index in target){
            return target[index]
        }else if(index < 0 && Math.abs(index) <= target.length){
            return target[target.length + index]
        }
        throw new Error("p is not in target")
    }
})
console.log(proxy[-2]) //4
console.log(proxy[2]) //3

2.set()

用於攔截某個屬性的賦值操作,可以接受四個參數,分別是:目標對象、屬性名、屬性值和 proxy 本身。

如果通過 obj.xxx 或者 obj[xxx] 的方式給一個對象添加屬性或者設置屬性時,我們無法對屬性值進行校驗過濾,然而通過該方法就可以實現這個需求了。

let proxy = new Proxy({},{
    get(target,p){
        if(p in target)
            return target[p]
        throw new Error("p is not in target")
    },
    set(target,key,value){
        if(key === "age"){
            if(Object.prototype.toString.call(value).slice(8,-1) !== "Number")
                throw new Error("age is not a number")
            if(value > 200 || value < 0)
                throw new Error("you are not a man")
        }
        target[key] = value
    }
})
proxy.age = 88
console.log(proxy.age) //88

上面這裏對 age 屬性的值進行了檢驗以及限制。

3.apply()

該方法可以攔截函數的調用、call()apply()

該方法接受三個參數,分別是:目標對象、目標對象的上下文對象和目標對象的參數列表。

let target = function () {
    console.log("target")
}
let proxy = new Proxy(target,{
    apply(target, thisArg, argArray) {
        console.log("proxy")
    }
})
proxy() //proxy

4.has()

該方法用於攔截 hasProperty 操作,判斷對象是否具有某個屬性時,這個方法會生效。典型的案例就是 in 運算符。但對 for...in 不生效。

該方法可以接受兩個參數,分別是目標對象、屬性名。

5.construct()

攔截 new 命令。該方法可以接受三個參數,分別是:目標對象,構造函數的參數列表,構造函數。

該方法必須返回一個對象,否則會報錯。

6.deleteProperty()

用於攔截 delete 操作,如果這個方法拋出錯誤或者返回 false,當前屬性就無法被刪除。

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