JS?中Proxy代理和?Reflect反射方法示例詳解
正文
總所周知,Vue2 => Vue3 時,數(shù)據響應式方法從Object.defineProperty()
方法變成了Proxy()
,所以今天與大家 Proxy(代理)和 Reflect(反射)的知識。
講解 Proxy 和 Reflect 前,我們需要先了解屬性描述符的作用,所以我們線簡單解釋一下屬性描述符的知識。
1.屬性描述符
屬性描述符(Property Descriptor) 本質上是一個 JavaScript 普通對象,用于描述一個屬性的相關信息,共有下面這幾種屬性。
- value:屬性值
- configurable:該屬性的描述符是否可以修改
- enumerable:該屬性是否可以被枚舉
- writable:該屬性是否可以被重新賦值
- 存取器屬性:屬性描述符中如果配置了 get 和 set 中的任何一個,則該屬性不再是一個普通屬性,而變成了存取器屬性。
- get()讀值函數(shù):如果一個屬性是存取器屬性,則讀取該屬性時,會運行 get 方法,并將 get 方法得到的返回值作為屬性值
- set(newVal)存值函數(shù):如果給該屬性賦值,則會運行 set 方法,newVal 參數(shù)為賦值的值。
存取器屬性最大的意義,在于可以控制屬性的讀取和賦值,在函數(shù)里可以進行各種操作。
Vue2 數(shù)據響應式就是使用了這一點,在 getter 和 setter 函數(shù)中進行了數(shù)據綁定與派發(fā)更新。
注意點:value 和 writable 屬性不能與 get 和 set 屬性二者不可共存,二者只能選其一。
查看某個對象的屬性描述符,使用以下這兩種方法:
Object.getOwnPropertyDescriptor(對象, 屬性名); //得到一個對象的某個屬性的屬性描述符 Object.getOwnPropertyDescriptors(對象); //得到某個對象的所有屬性描述符
為某個對象添加屬性時 或 修改屬性時,配置其屬性描述符,使用以下這兩種方法:
Object.defineProperty(對象, 屬性名, 描述符); //設置一個對象的某個屬性 Object.defineProperties(對象, 多個屬性的描述符); //設置一個對象的多個屬性
2.Reflect
Reflect 是什么? Reflect 是一個內置的 JS 對象,它提供了一系列方法,可以讓開發(fā)者通過調用這些方法,訪問一些 JS 底層功能。
由于它類似于其他語言的反射,因此取名為 Reflect。
它可以做什么? 使用 Reflect 可以實現(xiàn)諸如:屬性的賦值與取值、調用普通函數(shù)、調用構造函數(shù)、判斷屬性是否存在與對象中 等等功能。
這些功能不是已經存在了嗎?為什么還需要用 Reflect 實現(xiàn)一次? 有一個重要的理念,在 ES5 就被提出:減少魔法、讓代碼更加純粹(語言的方法使用 API 實現(xiàn),而不使用特殊語法實現(xiàn)),這種理念很大程度上是受到函數(shù)式編程的影響。 ES6 進一步貫徹了這種理念,它認為,對屬性內存的控制、原型鏈的修改、函數(shù)的調用等等,這些都屬于底層實現(xiàn),屬于一種魔法,因此,需要將它們提取出來,形成一個正常的 API,并高度聚合到某個對象中,于是就造就了 Reflect 對象。 因此,你可以看到 Reflect 對象中有很多的 API 都可以使用過去的某種語法或其他 API 實現(xiàn)。
Reflect 里面提供了哪些 API 呢?
Reflect API | 用處 | 等同于 |
---|---|---|
Reflect.get(target, propertyKey) | 讀取對象 target 的屬性 propertyKey | 對象的屬性值讀取操作 |
Reflect.set(target, propertyKey, value) | 設置對象 target 的屬性 propertyKey 的值為 value | 對象的屬性賦值操作 |
Reflect.has(target, propertyKey) | 判斷一個對象是否擁有一個屬性 | in 操作符 |
Reflect.defineProperty(target, propertyKey, attributes) | 類似于 Object.defineProperty,不同的是如果配置出現(xiàn)問題,返回 false 而不是報錯 | Object.defineProperty |
Reflect.deleteProperty(target, propertyKey) | 刪除一個對象的屬性 | delete 操作符 |
Reflect.apply(target, thisArgument, argumentsList) | 調用一個指定的函數(shù),并綁定 this 和參數(shù)列表 | 函數(shù)調用操作 |
Reflect.construct(target, argumentsList) | 用構造函數(shù)的方式創(chuàng)建一個對象 | new 操作符 |
3.Proxy
ECMAScript 6 新增的代理和反射為開發(fā)者提供了攔截并向基本操作嵌入額外行為的能力。
具體地說,可以給目標對象(target)定義一個關聯(lián)的代理對象,而這個代理對象可當作一個抽象的目標對象來使用。
因此在對目標對象的各種操作影響到目標對象之前,我們可以在代理對象中對這些操作加以控制,并且最終也可能會改變操作返回的結果。
所以我的理解是:代理(Proxy)能使我們開發(fā)者擁有一種間接修改底層方法的能力,從而控制用戶的操作。
3.1 創(chuàng)建空代理
最簡單的代理是空代理,即除了作為一個抽象的目標對象,什么也不做。 默認情況下,在代理對象上執(zhí)行的所有操作都會無障礙地傳播到目標對象。因此,在任何可以使用目標對象的地方,都可以通過同樣的方式來使用與之關聯(lián)的代理對象。
代理是使用 Proxy 構造函數(shù)創(chuàng)建的,這個構造函數(shù)接收兩個參數(shù):目標對象和處理程序對象。缺 少其中任何一個參數(shù)都會拋出 TypeError。返回一個代理對象 如:new Proxy(target, handler);
要創(chuàng)建空代理,可以傳一個簡單的對象字面量作為處理程序對象,從而讓所有操作暢通無阻地抵達目標對象。
const target = { id: 'target' }; //target:目標對象 const handler = {}; //handler:是一個普通對象,其中可以重寫底層實現(xiàn) //創(chuàng)建空對象 const proxy = new Proxy(target, handler); // id 屬性會訪問同一個值 console.log(target.id); // target console.log(proxy.id); // target // 給目標屬性賦值會反映在兩個對象上 因為兩個對象訪問的是同一個值 target.id = 'foo'; console.log(target.id); // foo console.log(proxy.id); // foo // 給代理屬性賦值會反映在兩個對象上 因為這個賦值會轉移到目標對象 proxy.id = 'bar'; console.log(target.id); // bar console.log(proxy.id); // bar // hasOwnProperty()方法在兩個地方 也都會應用到目標對象 console.log(target.hasOwnProperty('id')); // true console.log(proxy.hasOwnProperty('id')); // true // Proxy.prototype 是 undefined 因此不能使用 instanceof 操作符 console.log(target instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check // 嚴格相等可以用來區(qū)分代理和目標 console.log(target === proxy); // false
3.2 定義捕獲器
使用代理的主要目的是可以定義捕獲器(trap)。捕獲器就是在處理程序對象中定義的“基本操作的攔截器”。
每個處理程序對象可以包含零個或多個捕獲器,每個捕獲器都對應一種基本操作,可以直接或間接在代理對象上調用。
每次在代理對象上調用這些基本操作時,代理可以在這些操作傳播到目標對象之前先調用捕獲器函數(shù),從而攔截并修改相應的行為。
所有捕獲器都可以訪問相應的參數(shù),基于這些參數(shù)可以重建被捕獲方法的原始行為。比如,get()捕獲器會接收到目標對象、要查詢的屬性和代理對象三個參數(shù)。
const target = { foo: "bar", }; const handler = { // 捕獲器在處理程序對象中以方法名為鍵 get(trapTarget, property, receiver) { //trapTarget - 目標對象 //property - 要查詢的屬性 //receiver - 代理對象 return "handler override"; }, }; const proxy = new Proxy(target, handler); console.log(target.foo); // bar console.log(proxy.foo); // handler override
3.3 捕獲器不變式
使用捕獲器幾乎可以改變所有基本方法的行為,但也不是沒有限制。
根據 ECMAScript 規(guī)范,每個捕獲的方法都知道目標對象上下文、捕獲函數(shù)簽名,而捕獲處理程序的行為必須遵循“捕獲器不變式”(trap invariant)。捕獲器不變式因方法不同而異,但通常都會防止捕獲器定義出現(xiàn)過于反常的行為。
比如,如果目標對象有一個不可配置且不可寫的數(shù)據屬性,那么在捕獲器返回一個與該屬性不同的值時,會拋出 TypeError:
const target = {}; Object.defineProperty(target, "foo", { configurable: false, writable: false, value: "bar", }); const handler = { get() { return "qux"; }, }; const proxy = new Proxy(target, handler); console.log(proxy.foo); // TypeError
3.4 可撤銷代理
有時候可能需要中斷代理對象與目標對象之間的聯(lián)系。
Proxy 也暴露了 revocable()方法,這個方法支持撤銷代理對象與目標對象的關聯(lián)。
后續(xù)可直接調用撤銷函數(shù) revoke() 來撤銷代理。
撤銷代理之后再調用代理會拋出 TypeError,撤銷函數(shù)和代理對象是在實例化時同時生成的:
const target = { foo: "bar", }; const handler = { get() { return "intercepted"; }, }; const { proxy, revoke } = Proxy.revocable(target, handler); console.log(proxy.foo); // intercepted console.log(target.foo); // bar revoke(); console.log(proxy.foo); // TypeError
4.代理捕獲器與反射方法
4.1 get()
get()捕獲器會在獲取屬性值的操作中被調用。對應的反射 API 方法為 Reflect.get()
const myTarget = {}; const proxy = new Proxy(myTarget, { get(target, property, receiver) { console.log("get()"); return Reflect.get(...arguments); }, }); proxy.foo; // 觸發(fā)get()捕獲器
返回值 返回值無限制。
攔截的操作
- proxy.property
- proxy[property]
- Object.create(proxy)[property]
- Reflect.get(proxy, property, receiver)
捕獲器處理程序參數(shù)
- target:目標對象。
- property:引用的目標對象上的字符串鍵屬性。① - receiver:代理對象或繼承代理對象的對象。
捕獲器不變式 如果 target.property 不可寫且不可配置,則處理程序返回的值必須與 target.property 匹配。 如果 target.property 不可配置且[[Get]]特性為 undefined,處理程序的返回值也必須是 undefined。
4.2 set()
set()捕獲器會在設置屬性值的操作中被調用。對應的反射 API 方法為 Reflect.set()。
const myTarget = {}; const proxy = new Proxy(myTarget, { set(target, property, value, receiver) { console.log("set()"); return Reflect.set(...arguments); }, }); proxy.foo = "bar"; // 觸發(fā)set()捕獲器
返回值 返回 true 表示成功;返回 false 表示失敗,嚴格模式下會拋出 TypeError。
攔截的操作
- proxy.property = value
- proxy[property] = value
- Object.create(proxy)[property] = value
- Reflect.set(proxy, property, value, receiver)
捕獲器處理程序參數(shù)
- target:目標對象。
- property:引用的目標對象上的字符串鍵屬性。
- value:要賦給屬性的值。
- receiver:接收最初賦值的對象。
捕獲器不變式 如果 target.property 不可寫且不可配置,則不能修改目標屬性的值。 如果 target.property 不可配置且[[Set]]特性為 undefined,則不能修改目標屬性的值。 在嚴格模式下,處理程序中返回 false 會拋出 TypeError。
4.3 has()
has()捕獲器會在 in 操作符中被調用。對應的反射 API 方法為 Reflect.has()。
const myTarget = {}; const proxy = new Proxy(myTarget, { has(target, property) { console.log("has()"); return Reflect.has(...arguments); }, }); "foo" in proxy; //觸發(fā) has()捕獲器
返回值 has()必須返回布爾值,表示屬性是否存在。返回非布爾值會被轉型為布爾值。
攔截的操作
- property in proxy
- property in Object.create(proxy)
- with(proxy) {(property);}
- Reflect.has(proxy, property)
捕獲器處理程序參數(shù)
- target:目標對象。
- property:引用的目標對象上的字符串鍵屬性。
捕獲器不變式 如果 target.property 存在且不可配置,則處理程序必須返回 true。 如果 target.property 存在且目標對象不可擴展,則處理程序必須返回 true。
4.4 deleteProperty()
deleteProperty()捕獲器會在 delete 操作符中被調用。對應的反射 API 方法為 Reflect.deleteProperty()。
const myTarget = {}; const proxy = new Proxy(myTarget, { deleteProperty(target, property) { console.log("deleteProperty()"); return Reflect.deleteProperty(...arguments); }, }); delete proxy.foo; // 觸發(fā)deleteProperty()捕獲器
- 返回值 deleteProperty()必須返回布爾值,表示刪除屬性是否成功。返回非布爾值會被轉型為布爾值。
- 攔截的操作
- delete proxy.property
- delete proxy[property]
- Reflect.deleteProperty(proxy, property)
- 捕獲器處理程序參數(shù)
- target:目標對象。
- property:引用的目標對象上的字符串鍵屬性。
- 捕獲器不變式 如果自有的 target.property 存在且不可配置,則處理程序不能刪除這個屬性。
4.5 apply()
apply()捕獲器會在調用函數(shù)時中被調用。對應的反射 API 方法為 Reflect.apply()。
const myTarget = () => {}; const proxy = new Proxy(myTarget, { apply(target, thisArg, ...argumentsList) { console.log("apply()"); return Reflect.apply(...arguments); }, }); proxy(); // 觸發(fā)apply()捕獲器
- 返回值 返回值無限制。
- 攔截的操作
- proxy(...argumentsList)
- Function.prototype.apply(thisArg, argumentsList)
- Function.prototype.call(thisArg, ...argumentsList)
- Reflect.apply(target, thisArgument, argumentsList)
- 捕獲器處理程序參數(shù)
- target:目標對象。
- thisArg:調用函數(shù)時的 this 參數(shù)。
- argumentsList:調用函數(shù)時的參數(shù)列表
- 捕獲器不變式 target 必須是一個函數(shù)對象。
4.6 construct()
construct()捕獲器會在 new 操作符中被調用。對應的反射 API 方法為 Reflect.construct()。
const myTarget = function () {}; const proxy = new Proxy(myTarget, { construct(target, argumentsList, newTarget) { console.log("construct()"); return Reflect.construct(...arguments); }, }); new proxy(); // 觸發(fā)construct()捕獲器
- 返回值 construct()必須返回一個對象。
- 攔截的操作
- new proxy(...argumentsList)
- Reflect.construct(target, argumentsList, newTarget)
- 捕獲器處理程序參數(shù)
- target:目標構造函數(shù)
- argumentsList:傳給目標構造函數(shù)的參數(shù)列表。
- newTarget:最初被調用的構造函數(shù)。
- 捕獲器不變式 target 必須可以用作構造函數(shù)。
還有另外七種捕獲器:
- defineProperty()捕獲器會在 Object.defineProperty()中被調用。
- getOwnPropertyDescriptor()捕獲器會在 Object.getOwnPropertyDescriptor()中被調 用。
- ownKeys()捕獲器會在 Object.keys()及類似方法中被調用。
- getPrototypeOf()捕獲器會在 Object.getPrototypeOf()中被調用。
- setPrototypeOf()捕獲器會在 Object.setPrototypeOf()中被調用。
- isExtensible()捕獲器會在 Object.isExtensible()中被調用。
- preventExtensions()捕獲器會在 Object.preventExtensions()中被調用。
這七種捕獲器詳細介紹可參考MDN - Proxy。
參考博客
《Javascript 高級程序設計(第 4 版)》(JS 紅寶書)
以上就是JS 中Proxy代理和 Reflect反射方法示例詳解的詳細內容,更多關于JS Proxy代理Reflect反射的資料請關注腳本之家其它相關文章!
相關文章
javascript 函數(shù)聲明與函數(shù)表達式的區(qū)別介紹
javascript中的函數(shù)聲明與函數(shù)表達式使用比較頻繁,可能很多的朋友都不知道他們之間的區(qū)別,在此為大家詳細介紹下,希望對大家有所幫助2013-10-10Code:loadScript( )加載js的功能函數(shù)
Code:loadScript( )加載js的功能函數(shù)...2007-02-02