
1.4 代理與反射Proxy代理與 Reflect反射是 ES6 引入的元編程核心能力二者配套使用為 JavaScript 提供了攔截、自定義對(duì)象基礎(chǔ)操作的能力。Proxy 負(fù)責(zé)架設(shè)操作攔截層Reflect 提供標(biāo)準(zhǔn)化的對(duì)象操作方法二者共同構(gòu)成了現(xiàn)代前端響應(yīng)式系統(tǒng)、數(shù)據(jù)校驗(yàn)、權(quán)限控制的底層實(shí)現(xiàn)基礎(chǔ)也是理解主流框架底層原理的關(guān)鍵知識(shí)點(diǎn)。1.4.1 Proxy 代理對(duì)象Proxy 可以在目標(biāo)對(duì)象外層創(chuàng)建一個(gè)代理對(duì)象外部對(duì)目標(biāo)對(duì)象的所有操作屬性讀寫(xiě)、刪除、遍歷、函數(shù)調(diào)用等都會(huì)先經(jīng)過(guò)代理層的攔截開(kāi)發(fā)者可以在攔截邏輯中自定義行為實(shí)現(xiàn)對(duì)對(duì)象操作的管控與擴(kuò)展。一、基礎(chǔ)語(yǔ)法const proxy new Proxy(target, handler);target被代理的目標(biāo)對(duì)象可以是普通對(duì)象、數(shù)組、函數(shù)、類(lèi)等所有引用類(lèi)型handler攔截器配置對(duì)象通過(guò)定義不同的陷阱trap方法攔截對(duì)應(yīng)操作返回值代理對(duì)象外觀與原對(duì)象一致操作會(huì)被 handler 攔截二、常用攔截方法陷阱Proxy 支持 13 種攔截操作覆蓋對(duì)象的所有基礎(chǔ)行為前端交互開(kāi)發(fā)中高頻使用的有以下 6 種攔截方法觸發(fā)時(shí)機(jī)典型用途get(target, prop, receiver)讀取代理對(duì)象屬性時(shí)數(shù)據(jù)讀取攔截、默認(rèn)值補(bǔ)全、屬性代理set(target, prop, value, receiver)設(shè)置代理對(duì)象屬性時(shí)數(shù)據(jù)校驗(yàn)、變更監(jiān)聽(tīng)、狀態(tài)同步has(target, prop)in運(yùn)算符判斷屬性時(shí)隱藏內(nèi)部屬性、存在性攔截deleteProperty(target, prop)delete刪除屬性時(shí)刪除權(quán)限控制、刪除行為監(jiān)聽(tīng)apply(target, thisArg, args)代理函數(shù)被調(diào)用時(shí)函數(shù)參數(shù)校驗(yàn)、結(jié)果包裝、埋點(diǎn)統(tǒng)計(jì)construct(target, args)new實(shí)例化代理類(lèi)時(shí)實(shí)例化攔截、統(tǒng)一初始化邏輯基礎(chǔ)示例屬性讀寫(xiě)攔截const user { name: 張三, age: 25 }; const userProxy new Proxy(user, { // 攔截讀取操作 get(target, prop) { console.log(讀取了屬性${prop}); // 屬性不存在時(shí)返回默認(rèn)值 return prop in target ? target[prop] : 未知屬性; }, // 攔截賦值操作 set(target, prop, value) { console.log(設(shè)置屬性${prop} ${value}); // 年齡字段做數(shù)值校驗(yàn) if (prop age typeof value ! number) { throw new TypeError(年齡必須是數(shù)字); } target[prop] value; return true; // set 攔截必須返回布爾值表示操作是否成功 } }); console.log(userProxy.name); // 打印讀取了屬性name → 張三 console.log(userProxy.gender); // 打印讀取了屬性gender → 未知屬性 userProxy.age 26; // 打印設(shè)置屬性age 26三、可撤銷(xiāo)代理通過(guò)Proxy.revocable()可以創(chuàng)建可撤銷(xiāo)的代理對(duì)象調(diào)用撤銷(xiāo)方法后代理將完全失效所有操作都會(huì)拋出錯(cuò)誤適合臨時(shí)授權(quán)、生命周期管控場(chǎng)景。const { proxy, revoke } Proxy.revocable({ a: 1 }, {}); console.log(proxy.a); // 1 revoke(); // 撤銷(xiāo)代理 console.log(proxy.a); // 拋出錯(cuò)誤Cannot perform get on a proxy that has been revoked四、核心特性全對(duì)象代理代理的是整個(gè)對(duì)象而非單個(gè)屬性可自動(dòng)攔截新增屬性無(wú)需提前遍歷劫持全類(lèi)型支持支持代理數(shù)組、Map、Set、函數(shù)、類(lèi)等所有引用類(lèi)型引用隔離代理對(duì)象與原對(duì)象是不同的引用直接修改原對(duì)象不會(huì)觸發(fā)攔截透明代理未配置攔截的操作會(huì)直接透?jìng)鞯皆瓕?duì)象行為與原對(duì)象完全一致1.4.2 Reflect 反射 APIReflect 是一個(gè)內(nèi)置的靜態(tài)對(duì)象提供了一系列操作對(duì)象的標(biāo)準(zhǔn)化函數(shù)方法。它將原本散落在Object上的命令式操作、運(yùn)算符行為統(tǒng)一為函數(shù)式調(diào)用且所有方法與 Proxy 的攔截方法一一對(duì)應(yīng)是 Proxy 攔截邏輯的最佳配套實(shí)現(xiàn)。一、核心設(shè)計(jì)價(jià)值統(tǒng)一對(duì)象操作 API將in、delete、new等運(yùn)算符行為轉(zhuǎn)為函數(shù)調(diào)用風(fēng)格統(tǒng)一更易編程化處理合理的返回值操作結(jié)果返回布爾值表示成功 / 失敗替代傳統(tǒng)方法的報(bào)錯(cuò)行為更易做異常處理完美配套 Proxy與 Proxy 陷阱方法一一對(duì)應(yīng)可在攔截器中通過(guò) Reflect 執(zhí)行對(duì)象的默認(rèn)行為簡(jiǎn)化代碼修正 this 指向支持傳入receiver參數(shù)正確處理 getter/setter 中的 this 指向問(wèn)題二、常用方法與對(duì)應(yīng)關(guān)系Reflect 的方法與 Proxy 攔截器完全一一對(duì)應(yīng)前端高頻使用的方法如下Reflect方法等價(jià)傳統(tǒng)操作差異說(shuō)明Reflect.get(target, prop, receiver)target[prop]可指定 receiver 修正 this 指向Reflect.set(target, prop, value, receiver)target[prop] value返回布爾值表示操作是否成功Reflect.has(target, prop)prop in target函數(shù)式調(diào)用語(yǔ)義更清晰Reflect.deleteProperty(target, prop)delete target[prop]返回布爾值表示操作是否成功Reflect.ownKeys(target)Object.getOwnPropertyNamesObject.getOwnPropertySymbols返回所有自有屬性鍵Reflect.construct(target, args)new target(...args)函數(shù)式實(shí)例化支持指定原型最佳實(shí)踐Proxy Reflect 配套使用在 Proxy 攔截器中通過(guò) Reflect 執(zhí)行默認(rèn)操作保證原生行為不被破壞同時(shí)正確處理 this 指向與返回值規(guī)范。const data { count: 0 }; const proxy new Proxy(data, { get(target, prop, receiver) { console.log(觸發(fā)讀取, prop); // 用 Reflect 執(zhí)行默認(rèn)讀取邏輯保證 receiver 正確傳遞 return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { console.log(觸發(fā)設(shè)置, prop, value); // 直接返回 Reflect.set 的布爾結(jié)果符合 set 攔截的返回值要求 return Reflect.set(target, prop, value, receiver); } }); proxy.count; // 打印觸發(fā)讀取count → 觸發(fā)設(shè)置count 1三、與 Object 同名方法的差異以defineProperty為例Object.defineProperty操作失敗時(shí)會(huì)拋出錯(cuò)誤需要 try/catch 捕獲Reflect.defineProperty操作失敗時(shí)返回false可通過(guò)條件判斷處理代碼更優(yōu)雅const obj {}; Object.freeze(obj); // 傳統(tǒng)寫(xiě)法需捕獲異常 try { Object.defineProperty(obj, a, { value: 1 }); } catch (e) { console.log(操作失敗); } // Reflect 寫(xiě)法通過(guò)返回值判斷 const success Reflect.defineProperty(obj, a, { value: 1 }); if (!success) console.log(操作失敗);1.4.3 響應(yīng)式基礎(chǔ)原理響應(yīng)式是現(xiàn)代前端框架的核心特性當(dāng)數(shù)據(jù)發(fā)生變化時(shí)依賴該數(shù)據(jù)的視圖、副作用函數(shù)會(huì)自動(dòng)更新執(zhí)行。Vue 3、MobX 等主流框架的響應(yīng)式系統(tǒng)底層均基于 Proxy Reflect 實(shí)現(xiàn)。一、響應(yīng)式核心三要素?cái)?shù)據(jù)代理通過(guò) Proxy 代理數(shù)據(jù)對(duì)象攔截屬性的讀寫(xiě)操作依賴收集在讀取屬性get 攔截時(shí)記錄當(dāng)前正在執(zhí)行的副作用函數(shù)與該屬性的關(guān)聯(lián)關(guān)系依賴觸發(fā)在修改屬性set 攔截時(shí)找到該屬性關(guān)聯(lián)的所有副作用函數(shù)依次重新執(zhí)行二、極簡(jiǎn)響應(yīng)式實(shí)現(xiàn)以下是基于 Proxy 的最簡(jiǎn)響應(yīng)式 Demo可直觀理解核心原理// 存儲(chǔ)當(dāng)前正在執(zhí)行的副作用函數(shù) let activeEffect null; // 依賴收集桶target → prop → effect 集合 const targetMap new WeakMap(); // 1. 依賴收集讀取屬性時(shí)調(diào)用 function track(target, prop) { if (!activeEffect) return; let depsMap targetMap.get(target); if (!depsMap) targetMap.set(target, (depsMap new Map())); let deps depsMap.get(prop); if (!deps) depsMap.set(prop, (deps new Set())); deps.add(activeEffect); } // 2. 依賴觸發(fā)修改屬性時(shí)調(diào)用 function trigger(target, prop) { const depsMap targetMap.get(target); if (!depsMap) return; const deps depsMap.get(prop); deps deps.forEach(effect effect()); } // 3. 創(chuàng)建響應(yīng)式對(duì)象 function reactive(target) { return new Proxy(target, { get(target, prop, receiver) { track(target, prop); return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { const result Reflect.set(target, prop, value, receiver); trigger(target, prop); return result; } }); } // 4. 注冊(cè)副作用函數(shù) function effect(fn) { activeEffect fn; fn(); // 首次執(zhí)行觸發(fā)依賴收集 activeEffect null; } // 測(cè)試 const state reactive({ count: 0, name: 測(cè)試 }); // 注冊(cè)副作用數(shù)據(jù)變化時(shí)自動(dòng)重新執(zhí)行 effect(() { console.log(count 更新了, state.count); }); state.count; // 自動(dòng)打印count 更新了1 state.count 10; // 自動(dòng)打印count 更新了10三、Proxy 響應(yīng)式的優(yōu)勢(shì)對(duì)比 Vue 2 使用的Object.defineProperty基于 Proxy 的實(shí)現(xiàn)有本質(zhì)提升支持全量操作監(jiān)聽(tīng)可監(jiān)聽(tīng)屬性新增、刪除、數(shù)組下標(biāo)修改、長(zhǎng)度變化無(wú)需額外 hack支持集合類(lèi)型可代理 Map、Set、WeakMap、WeakSet覆蓋更多數(shù)據(jù)場(chǎng)景性能更優(yōu)代理整個(gè)對(duì)象無(wú)需初始化時(shí)遍歷所有屬性遞歸劫持能力更全面可攔截函數(shù)調(diào)用、實(shí)例化等更多操作擴(kuò)展能力更強(qiáng)