js中為什么Proxy一定要配合Reflect使用
引言
EcmaScript 2015 中引入了 Proxy 代理 與 Reflect 反射 兩個(gè)新的內(nèi)置模塊。
我們可以利用 Proxy 和 Reflect 來(lái)實(shí)現(xiàn)對(duì)于對(duì)象的代理劫持操作,類似于 Es 5 中 Object.defineProperty()的效果,不過(guò) Reflect & Proxy 遠(yuǎn)遠(yuǎn)比它強(qiáng)大。
大多數(shù)開(kāi)發(fā)者都了解這兩個(gè) Es6 中的新增內(nèi)置模塊,可是你也許并不清楚為什么 Proxy 一定要配合 Reflect 使用。
這里,文章通過(guò)幾個(gè)通俗易懂的例子來(lái)講述它們之間相輔相成的關(guān)系。
前置知識(shí)
Proxy 代理,它內(nèi)置了一系列”陷阱“用于創(chuàng)建一個(gè)對(duì)象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。
簡(jiǎn)單來(lái)說(shuō),我們可以通過(guò) Proxy 創(chuàng)建對(duì)于原始對(duì)象的代理對(duì)象,從而在代理對(duì)象中使用 Reflect 達(dá)到對(duì)于 JavaScript 原始操作的攔截。
如果你還不了解 & ,那么趕快去 MDN 上去補(bǔ)習(xí)他們的知識(shí)吧。
畢竟大名鼎鼎的 VueJs/Core 中核心的響應(yīng)式模塊就是基于這兩個(gè) Api 來(lái)實(shí)現(xiàn)的。
單獨(dú)使用 Proxy
開(kāi)始的第一個(gè)例子,我們先單獨(dú)使用 Proxy 來(lái)烹飪一道簡(jiǎn)單的開(kāi)胃小菜:
const obj = {
name: 'wang.haoyu',
};
const proxy = new Proxy(obj, {
// get陷阱中target表示原對(duì)象 key表示訪問(wèn)的屬性名
get(target, key) {
console.log('劫持你的數(shù)據(jù)訪問(wèn)' + key);
return target[key]
},
});
proxy.name // 劫持你的數(shù)據(jù)訪問(wèn)name -> wang.haoyu看起來(lái)很簡(jiǎn)單對(duì)吧,我們通過(guò) Proxy 創(chuàng)建了一個(gè)基于 obj 對(duì)象的代理,同時(shí)在 Proxy 中聲明了一個(gè) get 陷阱。
當(dāng)訪問(wèn)我們?cè)L問(wèn) proxy.name 時(shí)實(shí)際觸發(fā)了對(duì)應(yīng)的 get 陷阱,它會(huì)執(zhí)行 get 陷阱中的邏輯,同時(shí)會(huì)執(zhí)行對(duì)應(yīng)陷阱中的邏輯,最終返回對(duì)應(yīng)的 target[key] 也就是所謂的 wang.haoyu .
Proxy 中的 receiver
上邊的 Demo 中一切都看起來(lái)順風(fēng)順?biāo)疀](méi)錯(cuò)吧,細(xì)心的同學(xué)在閱讀 Proxy 的 MDN 文檔上可能會(huì)發(fā)現(xiàn)其實(shí) Proxy 中 get 陷阱中還會(huì)存在一個(gè)額外的參數(shù) receiver 。
那么這里的 receiver 究竟表示什么意思呢?大多數(shù)同學(xué)會(huì)將它理解成為代理對(duì)象,但這是不全面的。
接下來(lái)同樣讓我們以一個(gè)簡(jiǎn)單的例子來(lái)作為切入點(diǎn):
const obj = {
name: 'wang.haoyu',
};
const proxy = new Proxy(obj, {
// get陷阱中target表示原對(duì)象 key表示訪問(wèn)的屬性名
get(target, key, receiver) {
console.log(receiver === proxy);
return target[key];
},
});
// log: true
proxy.name;上述的例子中,我們?cè)?Proxy 實(shí)例對(duì)象的 get 陷阱上接收了 receiver 這個(gè)參數(shù)。
同時(shí),我們?cè)谙葳鍍?nèi)部打印 console.log(receiver === proxy); 它會(huì)打印出 true ,表示這里 receiver 的確是和代理對(duì)象相等的。
所以 receiver 的確是可以表示代理對(duì)象,但是這僅僅是 receiver 代表的一種情況而已。
接下來(lái)我們來(lái)看另外一個(gè)例子:
const parent = {
get value() {
return '19Qingfeng';
},
};
const proxy = new Proxy(parent, {
// get陷阱中target表示原對(duì)象 key表示訪問(wèn)的屬性名
get(target, key, receiver) {
console.log(receiver === proxy);
return target[key];
},
});
const obj = {
name: 'wang.haoyu',
};
// 設(shè)置obj繼承與parent的代理對(duì)象proxy
Object.setPrototypeOf(obj, proxy);
// log: false
obj.value關(guān)于原型上出現(xiàn)的 get/set 屬性訪問(wèn)器的“屏蔽”效果,我在這篇文章中進(jìn)行了詳細(xì)闡述。這里我就不展開(kāi)講解了。
我們可以看到,上述的代碼同樣我在 proxy 對(duì)象的 get 陷阱上打印了 console.log(receiver === proxy); 結(jié)果卻是 false 。
那么你可以稍微思考下這里的 receiver 究竟是什么呢? 其實(shí)這也是 proxy 中 get 陷阱第三個(gè) receiver 存在的意義。
它是為了傳遞正確的調(diào)用者指向,你可以看看下方的代碼:
...
const proxy = new Proxy(parent, {
// get陷阱中target表示原對(duì)象 key表示訪問(wèn)的屬性名
get(target, key, receiver) {
- console.log(receiver === proxy) // log:false
+ console.log(receiver === obj) // log:true
return target[key];
},
});
...其實(shí)簡(jiǎn)單來(lái)說(shuō),get 陷阱中的 receiver 存在的意義就是為了正確的在陷阱中傳遞上下文。
涉及到屬性訪問(wèn)時(shí),不要忘記 get 陷阱還會(huì)觸發(fā)對(duì)應(yīng)的屬性訪問(wèn)器,也就是所謂的 get 訪問(wèn)器方法。
我們可以清楚的看到上述的 receiver 代表的是繼承與 Proxy 的對(duì)象,也就是 obj。
看到這里,我們明白了 Proxy 中 get 陷阱的 receiver 不僅僅代表的是 Proxy 代理對(duì)象本身,同時(shí)也許他會(huì)代表繼承 Proxy 的那個(gè)對(duì)象。
其實(shí)本質(zhì)上來(lái)說(shuō)它還是為了確保陷阱函數(shù)中調(diào)用者的正確的上下文訪問(wèn),比如這里的 receiver 指向的是 obj 。
當(dāng)然,你不要將 revceiver 和 get 陷阱中的 this 弄混了,陷阱中的 this 關(guān)鍵字表示的是代理的 handler 對(duì)象。
比如:
const parent = {
get value() {
return '19Qingfeng';
},
};
const handler = {
get(target, key, receiver) {
console.log(this === handler); // log: true
console.log(receiver === obj); // log: true
return target[key];
},
};
const proxy = new Proxy(parent, handler);
const obj = {
name: 'wang.haoyu',
};
// 設(shè)置obj繼承與parent的代理對(duì)象proxy
Object.setPrototypeOf(obj, proxy);
// log: false
obj.valueReflect 中的 receiver
在清楚了 Proxy 中 get 陷阱的 receiver 后,趁熱打鐵我們來(lái)聊聊 Reflect 反射 API 中 get 陷阱的 receiver。
我們知道在 Proxy 中(以下我們都以 get 陷阱為例)第三個(gè)參數(shù) receiver 代表的是代理對(duì)象本身或者繼承與代理對(duì)象的對(duì)象,它表示觸發(fā)陷阱時(shí)正確的上下文。
const parent = {
name: '19Qingfeng',
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
return Reflect.get(target, key);
// 這里相當(dāng)于 return target[key]
},
};
const proxy = new Proxy(parent, handler);
const obj = {
name: 'wang.haoyu',
};
// 設(shè)置obj繼承與parent的代理對(duì)象proxy
Object.setPrototypeOf(obj, proxy);
// log: false
console.log(obj.value);我們稍微分析下上邊的代碼:
當(dāng)我們調(diào)用 obj.value 時(shí),由于 obj 本身不存在 value 屬性。
它繼承的 proxy 對(duì)象中存在 value 的屬性訪問(wèn)操作符,所以會(huì)發(fā)生屏蔽效果。
此時(shí)會(huì)觸發(fā) proxy 上的 get value() 屬性訪問(wèn)操作。
同時(shí)由于訪問(wèn)了 proxy 上的 value 屬性訪問(wèn)器,所以此時(shí)會(huì)觸發(fā) get 陷阱。
進(jìn)入陷阱時(shí),target 為源對(duì)象也就是 parent ,key 為 value 。
陷阱中返回
Reflect.get(target,key)相當(dāng)于target[key]。此時(shí),不知不覺(jué)中 this 指向在 get 陷阱中被偷偷修改掉了??!
原本調(diào)用方的 obj 在陷阱中被修改成為了對(duì)應(yīng)的 target 也就是 parent 。
自然而然打印出了對(duì)應(yīng)的
parent[value]也就是 19Qingfeng 。
這顯然不是我們期望的結(jié)果,當(dāng)我訪問(wèn) obj.value 時(shí),我希望應(yīng)該正確輸出對(duì)應(yīng)的自身上的 name 屬性也就是所謂的 obj.value => wang.haoyu 。
那么,Relfect 中 get 陷阱的 receiver 就大顯神通了。
const parent = {
name: '19Qingfeng',
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
- return Reflect.get(target, key);
+ return Reflect.get(target, key, receiver);
},
};
const proxy = new Proxy(parent, handler);
const obj = {
name: 'wang.haoyu',
};
// 設(shè)置obj繼承與parent的代理對(duì)象proxy
Object.setPrototypeOf(obj, proxy);
// log: wang.haoyu
console.log(obj.value);上述代碼原理其實(shí)非常簡(jiǎn)單:
首先,之前我們提到過(guò)在 Proxy 中 get 陷阱的 receiver 不僅僅會(huì)表示代理對(duì)象本身同時(shí)也還有可能表示繼承于代理對(duì)象的對(duì)象,具體需要區(qū)別與調(diào)用方。這里顯然它是指向繼承與代理對(duì)象的 obj 。
其次,我們?cè)?Reflect 中 get 陷阱中第三個(gè)參數(shù)傳遞了 Proxy 中的 receiver 也就是 obj 作為形參,它會(huì)修改調(diào)用時(shí)的 this 指向。
你可以簡(jiǎn)單的將
Reflect.get(target, key, receiver)理解成為target[key].call(receiver),不過(guò)這是一段偽代碼,但是這樣你可能更好理解。
相信看到這里你已經(jīng)明白 Relfect 中的 receiver 代表的含義是什么了,沒(méi)錯(cuò)它正是可以修改屬性訪問(wèn)中的 this 指向?yàn)閭魅氲?receiver 對(duì)象。

總結(jié)
相信看到這里大家都已經(jīng)明白了,為什么Proxy一定要配合Reflect使用。恰恰是為什么觸發(fā)代理對(duì)象的劫持時(shí)保證正確的 this 上下文指向。
我們?cè)賮?lái)稍稍回憶一下,針對(duì)于 get 陷阱(當(dāng)然 set 其他之類涉及到 receiver 的陷阱同理):
Proxy 中接受的 Receiver 形參表示代理對(duì)象本身或者繼承與代理對(duì)象的對(duì)象。
Reflect 中傳遞的 Receiver 實(shí)參表示修改執(zhí)行原始操作時(shí)的 this 指向。
結(jié)尾
這里就到了文章的結(jié)尾了,至于為什么會(huì)突然提到 Proxy & Reflect 的話題。
其實(shí)是筆者最近在閱讀 Vue/corejs 的源代碼內(nèi)容,剛好它內(nèi)部大量應(yīng)用于 Proxy & Reflect 所以就產(chǎn)生了這篇文章。
關(guān)于 Proxy 為什么一定要配合 Reflect 使用,具體結(jié)合 VueJs 中響應(yīng)式模塊的依賴收集其實(shí)會(huì)更好理解一些。不過(guò)這里為了照顧不太熟悉 VueJs 的同學(xué)所以就沒(méi)有展開(kāi)了。
當(dāng)然,最近我也在閱讀 VueJs 的過(guò)程中嘗試書(shū)寫一些階段性總結(jié)文章。之后在文章中也會(huì)詳細(xì)講解這一過(guò)程,有興趣的同學(xué)可以持續(xù)關(guān)注我的最新動(dòng)態(tài)~
到此這篇關(guān)于js中為什么Proxy一定要配合Reflect使用的文章就介紹到這了,更多相關(guān)js Proxy Reflect內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序購(gòu)物車、父子組件傳值及calc的注意事項(xiàng)總結(jié)
這篇文章主要給大家介紹了關(guān)于微信小程序購(gòu)物車、父子組件傳值及calc的注意事項(xiàng)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
輸入密碼檢測(cè)大寫是否鎖定js實(shí)現(xiàn)代碼
網(wǎng)站登錄為了更好的用戶體驗(yàn)都會(huì)在輸入密碼的時(shí)候檢測(cè)是否開(kāi)啟大寫,這樣有助于提醒用戶,需要學(xué)習(xí)的朋友可以參考下2012-12-12
一篇文章搞定JavaScript類型轉(zhuǎn)換(面試常見(jiàn))
這篇文章主要介紹了一篇文章搞定JavaScript類型轉(zhuǎn)換(面試常見(jiàn)),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-01-01
js中的值類型和引用類型小結(jié) 文字說(shuō)明與實(shí)例
下面就舉例講一下這兩種類型在JavaScript中的體現(xiàn)、用法及注意事項(xiàng)。2010-12-12
處理Axios返回Promise對(duì)象的幾種常見(jiàn)方式
Axios返回的是Promise對(duì)象,這意味著可以使用Promise的.then()、.catch()和.finally()方法來(lái)處理異步操作的結(jié)果,本文詳細(xì)介紹了處理Axios返回Promise對(duì)象的幾種常見(jiàn)方式,需要的朋友可以參考下2024-09-09
JavaScript實(shí)現(xiàn)動(dòng)態(tài)網(wǎng)頁(yè)飄落的雪花
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)動(dòng)態(tài)網(wǎng)頁(yè)飄落的雪花,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06

