1. 程式人生 > >Vue3.0 響應式資料原理:ES6 Proxy

Vue3.0 響應式資料原理:ES6 Proxy

> Vue3.0 開始用 Proxy 代替 Object.defineProperty了,這篇文章結合例項教你如何使用Proxy > 本篇文章同時收錄【前端知識點】中,[連結直達](https://github.com/programmer-zhang/front-end) ## 閱讀本文您將收穫 * `JavaScript` 中的 `Proxy` 是什麼?能幹什麼? * `Vue3.0` 開始為什麼用 `Proxy` 代替 `Object.defineProperty` ## `Proxy` 是什麼 > 解釋參考MDN,[連結直達](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy) ### 名詞解釋 * Proxy 物件用於定義基本操作的自定義行為(如屬性查詢、賦值、列舉、函式呼叫等) * Proxy 用於修改某些操作的預設行為,也可以理解為在目標物件之前架設一層攔截,外部所有的訪問都必須先通過這層攔截,因此提供了一種機制,可以對外部的訪問進行過濾和修改 ### 語法 * `const p = new Proxy(target, handler)` * `target`: 要使用 Proxy 包裝的目標物件(可以是任何型別的物件,包括原生陣列,函式,甚至另一個代理) * `handler`: 對該代理物件的各種操作行為處理(為空物件的情況下,基本可以理解為是對第一個引數做的一次**淺拷貝**) * 簡而言之:`target` 就是你想要代理的物件;而 `handler` 是一個函式物件,其中定義了所有你想替 `target` 代為管理的操作物件,包含了: * *`handler.has(target, prop)`: `in` 操作符的捕捉器,攔截HasProperty操作 * *`handler.get(target, prop)`: 屬性讀取操作的捕捉器 * *`handler.set(target, prop, value)`: 屬性設定操作的捕捉器 * *`handler.apply(target, object, args)`: 函式呼叫操作的捕捉器,攔截函式的呼叫、call和apply操作 * `handler.getPrototypeOf()`: `Object.getPrototypeOf` 方法的捕捉器 * `handler.setPrototypeOf()`: `Object.setPrototypeOf` 方法的捕捉器 * `handler.isExtensible()`: `Object.isExtensible` 方法的捕捉器 * `handler.preventExtensions()`: `Object.preventExtensions` 方法的捕捉器 * `handler.getOwnPropertyDescriptor()`: `Object.getOwnPropertyDescriptor` 方法的捕捉器 * `handler.defineProperty()`: `Object.defineProperty` 方法的捕捉器 * `handler.deleteProperty()`: `delete` 操作符的捕捉器 * `handler.ownKeys()`: `Object.getOwnPropertyNames` 方法和 `Object.getOwnPropertySymbols` 方法的捕捉器 * `handler.construct()`: new 操作符的捕捉器 * **注意**:如果一個屬性`不可配置` || `不可寫`,則該屬性不可被代理,通過 `Proxy` 訪問該屬性會報錯。 * **`*`** 標記的trap為本文都要涉及到的 ## `Proxy` 能幹什麼? ### 當你想進行以下操作時proxy模式通常會很有用: * 攔截或控制對某個物件的訪問 * 通過隱藏事務或輔助邏輯來減小方法/類的複雜性 * 防止在未經驗證/準備的情況下執行重度依賴資源的操作 ### 一:javascript中真正的私有變數/攔截has...in...操作/給出提示資訊或是阻止特定操作 ##### 傳統方法私有變數可獲取可修改 ##### Proxy 設定私有變數 * 針對私有變數,可以使用一個proxy來截獲針對某個屬性的請求並作出限制或是直接返回 `undefined ` * 還可以使用 `has` trap 來掩蓋這個屬性的存在 * `has` 方法攔截的是 `hasProperty` 操作,不是 `hasOwnProperty`,所以 `has...in` 方法不判斷一個屬性是自身屬性還是繼承的屬性 * **注意:** `has...in` 可以攔截到,`for...in` 攔截不到 * 阻止其他人刪除屬性,想讓呼叫方法的人知道該方法已經被廢棄,或是想阻止其他人修改屬性 * **注意**: 要是 `Proxy` 代理起作用,必須針對 `Proxy` 的例項進行操作,而不是針對目標物件進行操作 ### 二:資料校驗(看程式碼) * 利用 Proxy 代理進行簡單資料校驗
* 校驗邏輯直接加在代理處理函式中過於繁重,我們可以把校驗模組直接抽離出來,只需要去處理校驗的邏輯,代理層面後續不需要改動 ### 三:利用proxy進行記錄物件訪問 * 針對那些重度依賴資源,執行緩慢或是頻繁使用的方法或介面,統計它們的使用或是效能 * 可以記錄各種各樣的資訊而不用修改應用程式的程式碼或是阻塞程式碼執行。並且只需要在這些程式碼的基礎上稍事修改就可以記錄特性函式的執行效能 * 以上例子就是一個監聽函式執行的代理,可以將其進行擴充套件為打點函式 * 這裡面 `Proxy` 的 `trap` 為什麼使用 `get` 而不是 `apply` ? [答案](https://exploringjs.com/es6/ch_proxies.html#_intercepting-method-calls) ### 四:普通函式與建構函式的相容 * 建構函式呼叫沒有使用new關鍵字來呼叫的話,Class物件會直接丟擲異常 * 使用 `Proxy` 進行封裝讓建構函式也能夠直接進行函式呼叫
### 五:深層取值判斷(看程式碼) * 需要解決的幾個問題 1. 獲取資料進行攔截 2. `xxx.xxx.xxx...`無論 `undefined` 出現在哪裡都不能報錯 3. `Proxy` 的 `get()` 傳入的引數必須是物件 * 傳統方式深層取值繁瑣,利用Proxy可以簡化不必要程式碼 * 但是當 `target[prop]` 是 `undefined` 的時候,`Proxy get()`的入參變成了 `undefined`,但 `Proxy` 第一個入參必須為物件 * 需要對 obj 為 `undefined` 的時候進行特殊處理,為了能夠深層取值,所以使用一個空函式進行設定攔截,利用 `apply` trap 進行處理
* 我們理想中的應該是,如果屬性為 `undefined` 就返回 `undefined`,但仍要支援訪問下級屬性,而不是丟擲錯誤 * 順著這個思路來的話,很明顯當屬性為 `undefined` 的時候也需要用 `Proxy` 進行特殊處理 所以我們需要一個具有下面特性的 `get()` 方法 ``` getData(undefined)() === undefined; // true getData(undefined).xxx.yyy.zzz(); // undefined ``` * 這裡完全不需要注意 `get(undefined).xxx` 是否為正確的值,因為想獲取值必須要執行才能拿到 * 那麼只需要對所有 `undefined` 後面訪問的屬性都預設為 `undefined` 就好了,所以我們需要一個代理了 `undefined` 後的返回物件 * 同時為了解決無限迴圈執行的問題,當第一次檢測到出現 `undefined` 的時候,停止執行 ### 六:日誌上報 * [騰訊利用Proxy進行日誌上報功能](https://www.infoq.cn/article/6eKiic82aQu3uqaYvvfV) ## Vue 3.0 的 Proxy & Object.defineProperty ### Proxy * **劫持方式**:代理整個物件,只需做一層代理就可以監聽同級結構下的所有屬性變化,包括新增屬性和刪除屬性 * **本質**:`Proxy` 本質上屬於超程式設計非破壞性資料劫持,在原物件的基礎上進行了功能的衍生而又不影響原物件,符合鬆耦合高內聚的設計理念 ### Object.defineProperty * **劫持方式**:只能劫持物件的屬性,不能直接代理物件 * **流程**:get中進行依賴收集,set資料時通知訂閱者更新 * **存在的問題**:雖然 `Object.defineProperty` 通過為屬性設定 `getter/setter` 能夠完成資料的響應式,但是它並不算是實現資料的響應式的完美方案,某些情況下需要對其進行修補或者hack,這也是它的缺陷,主要表現在兩個方面: * 無法檢測到物件屬性的新增或刪除 * 不能監聽陣列的變化 ### 1. Object.defineProperty 無法監聽新增加的屬性 * 解決方式:提供方法重新手動Observe,需要監聽的話使用 `Vue.set()` 重新設定新增屬性的響應式 ### 2. Object.defineProperty 無法一次性監聽物件所有屬性,如物件屬性的子屬性 * 解決方式: 通過遞迴呼叫來實現子屬性響應式 ### 3. Object.defineProperty 無法響應陣列操作 * 解決方式:通過遍歷和重寫Array陣列原型方法操作方法實現,但是也只限制在 `push/pop/shift/unshift/splice/sort/reverse` 這七個方法,其他陣列方法及陣列的使用則無法檢測到,也無法監聽陣列索引的變化和長度的變更 ### 4. Proxy 攔截方式更多, Object.defineProperty 只有 get 和 set ### 5. Proxy 效能問題 * `Proxy` 的效能比 `Promise` 還差 * `Proxy` 作為新標準,從長遠來看,JS 引擎會繼續優化 `Proxy` * [Thoughts on ES6 Proxies Performance](https://thecodebarbarian.com/thoughts-on-es6-proxies-performance) * [ES6 Proxy 效能之我見](https://www.cnblogs.com/zmj97/p/10954968.html) ### 6. Proxy 相容性差 * `Vue 3.0` 中放棄了對於IE的支援(以為 `Vue 3.0` 中會對不相容的瀏覽器進行向下相容,但是經過檢視資料和原始碼發現尤大壓根沒做相容) * 目前並沒有一個完整支援 `Proxy` 所有攔截方法的 `Polyfill` 方案,有一個 `google` 編寫的 `proxy-polyfill` 也只支援了 `get/set/apply/construct` 四種攔截 ## 多說一嘴 Decorator * ES7 中實現的 `Decorator`,相當於設計模式中的裝飾器模式。 * 如果簡單地區分 `Proxy` 和 `Decorator` 的使用場景,可以概括為:`Proxy` 的核心作用是控制外界對被代理者內部的訪問,`Decorator` 的核心作用是增強被裝飾者的功能。 ## 寫在最後 * 如果你覺得這篇文章對你有益,煩請點贊以及分享給更多需要的人! ### 快到碗裡來!百度校招還有HC!甩簡歷來! ### 極速直接內推【位元組跳動】&【百度】&【猿輔導】&【京東】 ### 歡迎關注微信公眾號【全棧道路】,獲取更多科技相關知識及免費書籍。 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9wMy1qdWVqaW4uYnl0ZWltZy5jb20vdG9zLWNuLWktazN1MWZicGZjcC8wMWViZTUwZTVlNDg0NTFiOGNiNzI0MDgwMGVkYjJkMX50cGx2LWszdTFmYnBmY3Atem9vbS0xLmltYWdl?x-oss-process=image/format,png) ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9wMy1qdWVqaW4uYnl0ZWltZy5jb20vdG9zLWNuLWktazN1MWZicGZjcC82NTRiOGVhZjY4NDc0ODAxYjFmYjJmZWI1Y2IyZGE3Yn50cGx2LWszdTFmYnBmY3Atem9vbS0xLmltYWdl?x-oss-process=image/format,png) ## 更多好文 [幾行程式碼教你解決微信生成海報及二維碼](https://www.cnblogs.com/programmerzhang/p/14027702.html) [冷門的HTML - tabindex 的作用](https://www.cnblogs.com/programmerzhang/p/14022342.html) [[萬字長文]百度和好未來面試經含答案](https://blog.csdn.net/tufei_zhang/article/details/106519828) [[前端面試]前端快取問題看這篇,讓面試官愛上你](https://github.com/programmer-zhang/front-end/blob/master/profiles/HTTP_header.md) [記一次慘痛的Vue-cli + VueX + SSR經歷](https://github.com/programmer-zhang/front-end/blob/master/profiles/hmall.md) [[三分鐘小文]前端效能優化-HTML、CSS、JS部分](https://github.com/programmer-zhang/front-end/blob/master/profiles/%5B%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%5DHTML%E3%80%81CSS%E3%80%81JS%E7%AF%87.md) [[三分鐘小文]前端效能優化-頁面載入速度優化](https://github.com/programmer-zhang/front-end/blob/master/profiles/%5B%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%5D%E9%A1%B5%E9%9D%A2%E5%8A%A0%E8%BD%BD%E9%80%9F%E5%BA%A6%E4%BC%98%E5%8C%96.md) [[三分鐘小文]前端效能優化-網路傳輸層優化](https://github.com/programmer-zhang/front-end/blob/master/profiles/%5B%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%5D%E7%BD%91%E7%BB%9C%E4%BC%A0%E8%BE%93%E5%B1%82%E4%BC%98%E5%8C