代理(Proxy)和反射(Reflection)

??ES5和ES6致力于為開發(fā)者提供JS已有卻不可調(diào)用的功能。例如在ES5出現(xiàn)以前,JS環(huán)境中的對象包含許多不可枚舉和不可寫的屬性,但開發(fā)者不能定義自己的不可枚舉或不可寫屬性,于是ES5引入了Object.defineProperty()方法來支持開發(fā)者去做JS引擎早就可以實現(xiàn)的事情。ES6添加了一些內(nèi)建對象,賦予開發(fā)者更多訪問JS引擎的能力。代理(Proxy)是一種可以攔截并改變底層JS引擎操作的包裝器,在新語言中通過它暴露內(nèi)部運作的對象,從而讓開發(fā)者可以創(chuàng)建內(nèi)建的對象。

代理和反射

??調(diào)用new Proxy()可創(chuàng)建代替其他目標(target)對象的代理,它虛擬化了目標,所以二者看起來功能一致。
  代理可以攔截JS引擎內(nèi)部目標的底層對象操作,這些底層操作被攔截后會觸發(fā)響應特定操作的陷阱函數(shù)。
  反射API以Reflect對象的形式出現(xiàn),對象中方法的默認特性與相同的底層操作一致,而代理可以覆寫這些操作,每個代理陷阱對應一個命名和參數(shù)都相同的Reflect方法。下表總結(jié)了代理陷阱的特性。


get陷阱驗證對象結(jié)構(gòu)

??JS有一個時常令人感到困惑的特殊行為,即讀取不存在的屬性時不會拋出錯誤,而是用undefined代替被讀取屬性的值。
??當讀取屬性時,可以通過get陷阱來檢測,它接受3個參數(shù)

trapTarget 被讀取屬性的源對象(代理的目標)
key 要讀取的屬性鍵(字符串或Symbol)
receiver 操作發(fā)生的對象(通常是代理)
let proxy = new Proxy({}, {
    get(trapTarget, key, receiver) {
        if (!(key in receiver)) {
            throw new TypeError("Property " + key + " doesn't exist.");
        }
        return Reflect.get(trapTarget, key, receiver);
    }
});
// 添加屬性的功能正常
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
// 讀取不存在屬性會拋出錯誤
console.log(proxy.nme); // 拋出錯誤

set陷阱驗證屬性

??set陷阱接受4個參數(shù):

trapTarget 用于接收屬性(代理的目標)的對象
key 要寫入的屬性鍵(字符串或Symbol類型)
value 被寫入屬性的值
receiver 操作發(fā)生的對象(通常是代理)
let target = {
    name: "target"
};
let proxy = new Proxy(target, {
    set(trapTarget, key, value, receiver) {
        // 忽略已有屬性,避免影響它們
        if (!trapTarget.hasOwnProperty(key)) {
            if (isNaN(value)) {
                throw new TypeError("Property must be a number.");
            }
        }
        // 添加屬性
        return Reflect.set(trapTarget, key, value, receiver);
    }
});
// 添加一個新屬性
proxy.count = 1;
console.log(proxy.count); // 1
console.log(target.count); // 1
// 你可以為 name 賦一個非數(shù)值類型的值,因為該屬性已經(jīng)存在
proxy.name = "proxy";
console.log(proxy.name); // "proxy"
console.log(target.name); // "proxy"
// 拋出錯誤
proxy.anotherName = "proxy";

has陷阱隱藏已有屬性

??可用in操作符來檢測給定對象是否含有某個屬性,如果自有屬性或原型屬性匹配這個名稱或Symbol返回true。
??in操作符時會調(diào)用has陷阱,并傳入兩個參數(shù)

trapTarget 讀取屬性的對象(代理的目標)
key 要檢查的屬性鍵(字符串或Symbol)
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操作符刪除對象屬性時,deleteProperty陷阱都會被調(diào)用,它接受兩個參數(shù)。

trapTarget 要刪除屬性的對象(代理的目標)
key 要刪除的屬性鍵(字符串或Symbol)
let target = {
    name: "target",
    value: 42
};
let proxy = new Proxy(target, {
    deleteProperty(trapTarget, key) {
        if (key === "value") {
            return false;
        } else {
            return Reflect.deleteProperty(trapTarget, key);
        }
    }
});
// 嘗試刪除 proxy.value
console.log("value" in proxy); // true
let result1 = delete proxy.value;
console.log(result1); // false
console.log("value" in proxy); // true
// 嘗試刪除 proxy.name
console.log("name" in proxy); // true
let result2 = delete proxy.name;
console.log(result2); // true
console.log("name" in proxy); // false

原型代理陷阱

??Object.setPrototypeOf()方法被用于作為ES5中的Object.getPrototypeOf()方法的補充。通過代理中的setPrototypeOf陷阱和getPrototypeOf陷阱可以攔截這兩個方法的執(zhí)行過程,在這兩種情況下,Object上的方法會調(diào)用代理中的同名陷阱來改變方法的行為。
??兩個陷阱均與代理有關(guān),但具體到方法只與每個陷阱的類型有關(guān),setPrototypeOf陷阱接受以下這些參數(shù)。

trapTarget 接受原型設(shè)置的對象(代理的目標)
proto 作為原型使用的對象
let target = {};
let proxy = new Proxy(target, {
    getPrototypeOf(trapTarget) {
        return null;
    },
    setPrototypeOf(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
// 成功
Object.setPrototypeOf(target, {});
// 拋出錯誤
Object.setPrototypeOf(proxy, {});
let target = {};
let proxy = new Proxy(target, {
    getPrototypeOf(trapTarget) {
        return Reflect.getPrototypeOf(trapTarget);
    },
    setPrototypeOf(trapTarget, proto) {
        return Reflect.setPrototypeOf(trapTarget, proto);
    }
});
let targetProto = Object.getPrototypeOf(target);
let proxyProto = Object.getPrototypeOf(proxy);
console.log(targetProto === Object.prototype); // true
console.log(proxyProto === Object.prototype); // true
// 成功
Object.setPrototypeOf(target, {});
// 同樣成功
Object.setPrototypeOf(proxy, {});
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容