您好,登錄后才能下訂單哦!
這篇文章主要介紹了Javascript的Proxy與Reflect怎么調(diào)用的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Javascript的Proxy與Reflect怎么調(diào)用文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。
ECMAScript 在 ES6 規(guī)范中加入了 Proxy 與 Reflect 兩個(gè)新特性,這兩個(gè)新特性增強(qiáng)了 JavaScript 中對(duì)象訪問(wèn)的可控性,使得 JS 模塊、類的封裝能夠更加嚴(yán)密與簡(jiǎn)單,也讓操作對(duì)象時(shí)的報(bào)錯(cuò)變得更加可控。
Proxy,正如其名,代理。這個(gè)接口可以給指定的對(duì)象創(chuàng)建一個(gè)代理對(duì)象,對(duì)代理對(duì)象的任何操作,如:訪問(wèn)屬性、對(duì)屬性賦值、函數(shù)調(diào)用,都會(huì)被攔截,然后交由我們定義的函數(shù)來(lái)處理相應(yīng)的操作,
JavaScript 的特性讓對(duì)象有很大的操作空間,同時(shí) JavaScript 也提供了很多方法讓我們?nèi)ジ脑鞂?duì)象,可以隨意添加屬性、隨意刪除屬性、隨意更改對(duì)象的原型……但是此前 Object 類提供的 API 有許多缺點(diǎn):
如果要用 Object.defineProperty 定義某個(gè)名稱集合內(nèi)的全部屬性,只能通過(guò)枚舉的方式為全部屬性設(shè)置 getter 和 setter,而且由于只能每個(gè)屬性創(chuàng)造一個(gè)函數(shù),集合太大會(huì)造成性能問(wèn)題。
Object.defineProperty 定義后的屬性,如果仍想擁有正常的存取功能,只能將數(shù)據(jù)存放在對(duì)象的另一個(gè)屬性名上或者需要另一個(gè)對(duì)象來(lái)存放數(shù)據(jù),對(duì)于只想監(jiān)聽(tīng)屬性的場(chǎng)合尤為不便。
Object.defineProperty 無(wú)法修改類中不可重新定義的屬性,例如數(shù)組的 length 屬性。
對(duì)于那些尚不存在且名稱不好預(yù)測(cè)的屬性,Object.defineProperty 愛(ài)莫能助。
無(wú)法修改或阻止某些行為,如:枚舉屬性名、修改對(duì)象原型。
Proxy 接口的出現(xiàn)很好地解決了這些問(wèn)題:
Proxy 接口將對(duì)對(duì)象的所有操作歸類到數(shù)個(gè)類別中,通過(guò) Proxy 提供的陷阱攔截特定的操作,再在我們定義的處理函數(shù)中進(jìn)行邏輯判斷就可以實(shí)現(xiàn)復(fù)雜的功能,并且還能控制比以前更多的行為。
Proxy 創(chuàng)造的代理對(duì)象以中間人形式存在,其本身并不負(fù)責(zé)存放數(shù)據(jù),我們只需要提供代理對(duì)象給外部使用者,讓外部使用者在代理對(duì)象的控制下訪問(wèn)我們的原對(duì)象即可。
Proxy 接口在 JS 環(huán)境中是一個(gè)構(gòu)造函數(shù):
? Proxy ( target: Object, handlers: Object ) : Proxy
這個(gè)構(gòu)造函數(shù)有兩個(gè)參數(shù),第一個(gè)是我們要代理的對(duì)象,第二個(gè)是包含處理各種操作的函數(shù)的對(duì)象。
下面是調(diào)用示例:
//需要代理的目標(biāo) var target = { msg: "I wish I was a bird!" }; //包含處理各種操作的函數(shù)的對(duì)象 var handler = { //處理其中一種操作的函數(shù),此處是訪問(wèn)屬性的操作 get(target, property) { //在控制臺(tái)打印訪問(wèn)了哪個(gè)屬性 console.log(`你訪問(wèn)了 ${property} 屬性`); //實(shí)現(xiàn)操作的功能 return target[property]; } } //構(gòu)造代理對(duì)象 var proxy = new Proxy( target , handler); //訪問(wèn)代理對(duì)象 proxy.msg //控制臺(tái): 你訪問(wèn)了 msg 屬性 //← I wish I was a bird!
在上面的例子中,先創(chuàng)建了一個(gè)對(duì)象,賦值給 target ,然后再以 target 為目標(biāo)創(chuàng)建了一個(gè)代理對(duì)象,賦值給 proxy。在作為第二個(gè)參數(shù)提供給 Proxy 構(gòu)造函數(shù)的對(duì)象里有屬性名為“get”的屬性,是一個(gè)函數(shù),“get”是 Proxy 接口一個(gè)陷阱的名稱,Proxy 會(huì)參照我們作為第二個(gè)參數(shù)提供的對(duì)象里的屬性,找到那些屬性名與陷阱名相同的屬性,自動(dòng)設(shè)置相應(yīng)的陷阱并把屬性上的函數(shù)作為陷阱的處理函數(shù)。陷阱能夠攔截對(duì)代理對(duì)象的特定操作,把操作的細(xì)節(jié)轉(zhuǎn)換成參數(shù)傳遞給我們的處理函數(shù),讓處理函數(shù)去完成這一操作,這樣我們就可以通過(guò)處理函數(shù)來(lái)控制對(duì)象的各種行為。
在上面的例子里,構(gòu)造代理對(duì)象時(shí)提供的 get 函數(shù)就是處理訪問(wèn)對(duì)象屬性操作的函數(shù),代理對(duì)象攔截訪問(wèn)對(duì)象屬性的操作并給 get 函數(shù)傳遞目標(biāo)對(duì)象和請(qǐng)求訪問(wèn)的屬性名兩個(gè)參數(shù),并將函數(shù)的返回值作為訪問(wèn)的結(jié)果。
Proxy 的陷阱一共有13種:
陷阱名與對(duì)應(yīng)的函數(shù)參數(shù) | 攔截的操作 | 操作示例 |
---|---|---|
get(target, property) | 訪問(wèn)對(duì)象屬性 | target.property 或 target[property] |
set(target, property, value, receiver) | 賦值對(duì)象屬性 | target.property = value 或 target[property] = value |
has(target, property) | 判斷對(duì)象屬性是否存在 | property in target |
isExtensible(target) | 判斷對(duì)象可否添加屬性 | Object.isExtensible(target) |
preventExtensions(target) | 使對(duì)象無(wú)法添加新屬性 | Object.preventExtensions(target) |
defineProperty(target, property, descriptor) | 定義對(duì)象的屬性 | Object.defineProperty(target, property, descriptor) |
deleteProperty(target, property) | 刪除對(duì)象的屬性 | delete target.property 或 delete target[property] 或 Object.deleteProperty(target, property) |
getOwnPropertyDescriptor(target, property) | 獲取對(duì)象自有屬性的描述符 | Object.getOwnPropertyDescriptor(target, property) |
ownKeys(target) | 枚舉對(duì)象全部自有屬性 | Object.getOwnPropertyNames(target). concat(Object.getOwnPropertySymbols(target)) |
getPrototypeOf(target) | 獲取對(duì)象的原型 | Object.getPrototypeOf(target) |
setPrototypeOf(target) | 設(shè)置對(duì)象的原型 | Object.setPrototypeOf(target) |
apply(target, thisArg, argumentsList) | 函數(shù)調(diào)用 | target(...arguments) 或 target.apply(target, thisArg, argumentsList) |
construct(target, argumentsList, newTarget) | 構(gòu)造函數(shù)調(diào)用 | new target(...arguments) |
在上面列出的陷阱里是有攔截函數(shù)調(diào)用一類操作的,但是只限代理的對(duì)象是函數(shù)的情況下有效,Proxy 在真正調(diào)用我們提供的接管函數(shù)前是會(huì)進(jìn)行類型檢查的,所以通過(guò)代理讓普通的對(duì)象擁有函數(shù)一樣的功能這種事就不要想啦。
某一些陷阱對(duì)處理函數(shù)的返回值有要求,如果不符合要求則會(huì)拋出 TypeError 錯(cuò)誤。限于篇幅問(wèn)題,本文不深入闡述,需要了解可自行查找資料。
除了直接 new Proxy 對(duì)象外,Proxy 構(gòu)造函數(shù)上還有一個(gè)靜態(tài)函數(shù) revocable,可以構(gòu)造一個(gè)能被銷毀的代理對(duì)象。
Proxy.revocable( target: Object, handlers: Object ) : Object Proxy.revocable( target, handlers ) → { proxy: Proxy, revoke: ? () }
這個(gè)靜態(tài)函數(shù)接收和構(gòu)造函數(shù)一樣的參數(shù),不過(guò)它的返回值和構(gòu)造函數(shù)稍有不同,會(huì)返回一個(gè)包含代理對(duì)象和銷毀函數(shù)的對(duì)象,銷毀函數(shù)不需要任何參數(shù),我們可以隨時(shí)調(diào)用銷毀函數(shù)將代理對(duì)象和目標(biāo)對(duì)象的代理關(guān)系斷開(kāi)。斷開(kāi)代理后,再對(duì)代理對(duì)象執(zhí)行任何操作都會(huì)拋出 TypeError 錯(cuò)誤。
//創(chuàng)建代理對(duì)象 var temp1 = Proxy.revocable({a:1}, {}); //← {proxy: Proxy, revoke: ?} //訪問(wèn)代理對(duì)象 temp1.proxy.a //← 1 //銷毀代理對(duì)象 temp1.revoke(); //再次訪問(wèn)代理對(duì)象 temp1.proxy.a //未捕獲的錯(cuò)誤: TypeError: Cannot perform 'get' on a proxy that has been revoked
弄清楚了具體的原理后,下面舉例一個(gè)應(yīng)用場(chǎng)景。
假設(shè)某個(gè)需要對(duì)外暴露的對(duì)象上有你不希望被別人訪問(wèn)的屬性,就可以找代理對(duì)象作替身,在外部訪問(wèn)代理對(duì)象的屬性時(shí),針對(duì)不想被別人訪問(wèn)的屬性返回空值或者報(bào)錯(cuò):
//目標(biāo)對(duì)象 var target = { msg: "我是鮮嫩的美少女!", secret: "其實(shí)我是800歲的老太婆!" //不想被別人訪問(wèn)的屬性 }; //創(chuàng)建代理對(duì)象 var proxy = new Proxy( target , { get(target, property) { //如果訪問(wèn) secret 就報(bào)錯(cuò) if (property == "secret") throw new Error("不允許訪問(wèn)屬性 secret!"); return target[property]; } }); //訪問(wèn) msg 屬性 proxy.msg //← 我是鮮嫩的美少女! //訪問(wèn) secret 屬性 proxy.secret //未捕獲的錯(cuò)誤: 不允許訪問(wèn)屬性 secret!
在上面的例子中,我針對(duì)對(duì) secret 屬性的訪問(wèn)進(jìn)行了報(bào)錯(cuò),守護(hù)住了“美少女”的秘密,讓我們歌頌 Proxy 的偉大!
只不過(guò),Proxy 只是在程序邏輯上進(jìn)行了接管,上帝視角的控制臺(tái)依然能打印代理對(duì)象完整的內(nèi)容,真是遺憾……(不不不,這挺好的?。?/p>
proxy//控制臺(tái): Proxy {msg: '我是鮮嫩的美少女!', secret: '其實(shí)我是800歲的老太婆!'}
以下是關(guān)于 Proxy 的一些細(xì)節(jié)問(wèn)題:
Proxy 在處理屬性名的時(shí)候會(huì)把除 Symbol 類型外的所有屬性名都轉(zhuǎn)化成字符串,所以處理函數(shù)在判斷屬性名時(shí)需要尤其注意。
對(duì)代理對(duì)象的任何操作都會(huì)被攔截,一旦代理對(duì)象被創(chuàng)建就沒(méi)有辦法再修改它本身。
Proxy 的代理是非常底層的,在沒(méi)有主動(dòng)暴露原始目標(biāo)對(duì)象的情況下,沒(méi)有任何辦法越過(guò)代理對(duì)象訪問(wèn)目標(biāo)對(duì)象(在控制臺(tái)搞騷操作除外)。
Proxy 代理的目標(biāo)只能是對(duì)象,不能是 JavaScript 中的原始類型。
學(xué)過(guò)其他語(yǔ)言的人看到 Reflect 這個(gè)詞可能會(huì)首先聯(lián)想到“反射”這個(gè)概念,但 JavaScript 由于語(yǔ)言特性是不需要反射的,所以這里的 Reflect 其實(shí)和反射無(wú)關(guān),是 JavaScript 給 Proxy 配套的一系列函數(shù)。
Reflect 在 JS 環(huán)境里是一個(gè)全局對(duì)象,包含了與 Proxy 各種陷阱配套的函數(shù)。
Reflect: Object Reflect → { apply: ? apply(), construct: ? construct(), defineProperty: ? defineProperty(), deleteProperty: ? deleteProperty(), get: ? (), getOwnPropertyDescriptor: ? getOwnPropertyDescriptor(), getPrototypeOf: ? getPrototypeOf(), has: ? has(), isExtensible: ? isExtensible(), ownKeys: ? ownKeys(), preventExtensions: ? preventExtensions(), set: ? (), setPrototypeOf: ? setPrototypeOf(), Symbol(Symbol.toStringTag): "Reflect" }
可以看到,Reflect 上的所有函數(shù)都對(duì)應(yīng)一個(gè) Proxy 的陷阱。這些函數(shù)接受的參數(shù),返回值的類型,都和 Proxy 上的別無(wú)二致,可以說(shuō) Reflect 就是 Proxy 攔截的那些操作的原本實(shí)現(xiàn)。
那 Reflect 存在的意義是什么呢?
上文提到過(guò),Proxy 上某一些陷阱對(duì)處理函數(shù)的返回值有要求。如果想讓代理對(duì)象能正常工作,那就不得不按照 Proxy 的要求去寫(xiě)處理函數(shù)?;蛟S會(huì)有人覺(jué)得只要用 Object 提供的方法不就好了,然而不能這么想當(dāng)然,因?yàn)槟承┫葳逡蟮姆祷刂岛?Object 提供的方法拿到的返回值是不同的,而且有些陷阱還會(huì)有邏輯上的要求,和 Object 提供的方法的細(xì)節(jié)也有所出入。舉個(gè)簡(jiǎn)單的例子:Proxy 的 defineProperty 陷阱要求的返回值是布爾類型,成功就是 true,失敗就是 false。而 Object.defineProperty 在成功的時(shí)候會(huì)返回定義的對(duì)象,失敗則會(huì)報(bào)錯(cuò)。如此應(yīng)該能夠看出為陷阱編寫(xiě)實(shí)現(xiàn)的難點(diǎn),如果要求簡(jiǎn)單那自然是輕松,但是要求一旦復(fù)雜起來(lái)那真是想想都頭大,大多數(shù)時(shí)候我們其實(shí)只想過(guò)濾掉一部分操作而已。Reflect 就是專門(mén)為了解決這個(gè)問(wèn)題而提供的,因?yàn)?Reflect 里的函數(shù)都和 Proxy 的陷阱配套,返回值的類型也和 Proxy 要求的相同,所以如果我們要實(shí)現(xiàn)原本的功能,直接調(diào)用 Reflect 里對(duì)應(yīng)的函數(shù)就好了。
//需要代理的對(duì)象 var target = { get me() {return "我是鮮嫩的美少女!"} //定義 me 屬性的 getter }; //創(chuàng)建代理對(duì)象 var proxy = new Proxy( target , { //攔截定義屬性的操作 defineProperty(target, property, descriptor) { //如果定義的屬性是 me 就返回 false 阻止 if (property == "me") return false; //使用 Reflect 提供的函數(shù)實(shí)現(xiàn)原本的功能 return Reflect.defineProperty(target, property, descriptor); } }); //嘗試重新定義 me 屬性 Object.defineProperty(proxy , "me", {value: "我是800歲的老太婆!"}) //未捕獲的錯(cuò)誤: TypeError: 'defineProperty' on proxy: trap returned falsish for property 'me' //嘗試定義 age 屬性 Object.defineProperty(proxy , "age", {value: 17}) //← Proxy {age: 17} //使用 Reflect 提供的函數(shù)來(lái)定義屬性 Reflect.defineProperty(proxy , "me", {value: "我是800歲的老太婆!"}) //← false Reflect.defineProperty(proxy , "age", {value: 17}) //← true
在上面的例子里,由于我很懶,所以我在接管定義屬性功能的地方“偷工減料”用了 Reflect 提供的 defineProperty 函數(shù)。用 Object.defineProperty 在代理對(duì)象上定義 me 屬性時(shí)報(bào)了錯(cuò),表示失敗,而定義 age 屬性則成功完成了??梢钥吹剑吮粓?bào)錯(cuò)的 me 屬性,對(duì)其他屬性的定義是可以成功完成的。我還使用 Reflect 提供的函數(shù)執(zhí)行了同樣的操作,可以看到 Reflect 也無(wú)法越過(guò) Proxy 的代理,同時(shí)也顯示出了 Reflect 和傳統(tǒng)方法返回值的區(qū)別。
雖然 Reflect 的好處很多,但是它也有一個(gè)問(wèn)題:JS 全局上的 Reflect 對(duì)象是可以被修改的,可以替換掉里面的方法,甚至還能把 Reflect 刪掉。
//備份原本的 Reflect.get var originGet = Reflect.get; //修改 Reflect.get Reflect.get = function get(target ,property) { console.log("哈哈,你的 get 已經(jīng)是我的形狀了!"); return originGet(target ,property); }; //調(diào)用 Reflect.get Reflect.get({a:1}, "a") //控制臺(tái): 哈哈,你的 get 已經(jīng)是我的形狀了! //← 1 //刪除 Reflect 變量 delete Reflect //← true //訪問(wèn) Reflect 變量 Reflect //未捕獲的錯(cuò)誤: ReferenceError: Reflect is not defined
基于上面的演示,不難想到,可以通過(guò)修改 Reflect 以欺騙的方式越過(guò) Proxy 的代理。所以如果你對(duì)安全性有要求,建議在使用 Reflect 時(shí),第一時(shí)間將全局上的 Reflect 深度復(fù)制到你的閉包作用域并且只使用你的備份,或者將全局上的 Reflect 凍結(jié)并鎖定引用。
關(guān)于“Javascript的Proxy與Reflect怎么調(diào)用”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Javascript的Proxy與Reflect怎么調(diào)用”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。