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

全棧道路發表於2020-11-24

Vue3.0 開始用 Proxy 代替 Object.defineProperty了,這篇文章結合例項教你如何使用Proxy

本篇文章同時收錄【前端知識點】中,連結直達

閱讀本文您將收穫

  • JavaScript 中的 Proxy 是什麼?能幹什麼?
  • Vue3.0 開始為什麼用 Proxy 代替 Object.defineProperty

Proxy 是什麼

解釋參考MDN,連結直達

名詞解釋

  • 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...操作/給出提示資訊或是阻止特定操作

傳統方法私有變數可獲取可修改
Vue3.0 響應式資料原理:ES6 Proxy
Proxy 設定私有變數
  • 針對私有變數,可以使用一個proxy來截獲針對某個屬性的請求並作出限制或是直接返回 undefined
  • 還可以使用 has trap 來掩蓋這個屬性的存在
Vue3.0 響應式資料原理:ES6 Proxy
  • has 方法攔截的是 hasProperty 操作,不是 hasOwnProperty,所以 has...in 方法不判斷一個屬性是自身屬性還是繼承的屬性

  • 注意: has...in 可以攔截到,for...in 攔截不到

  • 阻止其他人刪除屬性,想讓呼叫方法的人知道該方法已經被廢棄,或是想阻止其他人修改屬性

Vue3.0 響應式資料原理:ES6 Proxy
  • 注意: 要是 Proxy 代理起作用,必須針對 Proxy 的例項進行操作,而不是針對目標物件進行操作

二:資料校驗(看程式碼)

  • 利用 Proxy 代理進行簡單資料校驗
Vue3.0 響應式資料原理:ES6 Proxy
  • 校驗邏輯直接加在代理處理函式中過於繁重,我們可以把校驗模組直接抽離出來,只需要去處理校驗的邏輯,代理層面後續不需要改動
    Vue3.0 響應式資料原理:ES6 Proxy

三:利用proxy進行記錄物件訪問

  • 針對那些重度依賴資源,執行緩慢或是頻繁使用的方法或介面,統計它們的使用或是效能

  • 可以記錄各種各樣的資訊而不用修改應用程式的程式碼或是阻塞程式碼執行。並且只需要在這些程式碼的基礎上稍事修改就可以記錄特性函式的執行效能
    Vue3.0 響應式資料原理:ES6 Proxy

  • 以上例子就是一個監聽函式執行的代理,可以將其進行擴充套件為打點函式

  • 這裡面 Proxytrap 為什麼使用 get 而不是 apply ? 答案

四:普通函式與建構函式的相容

  • 建構函式呼叫沒有使用new關鍵字來呼叫的話,Class物件會直接丟擲異常
  • 使用 Proxy 進行封裝讓建構函式也能夠直接進行函式呼叫
    Vue3.0 響應式資料原理:ES6 Proxy

五:深層取值判斷(看程式碼)

  • 需要解決的幾個問題

    1. 獲取資料進行攔截
    2. xxx.xxx.xxx...無論 undefined 出現在哪裡都不能報錯
    3. Proxyget() 傳入的引數必須是物件
  • 傳統方式深層取值繁瑣,利用Proxy可以簡化不必要程式碼
    Vue3.0 響應式資料原理:ES6 Proxy

  • 但是當 target[prop]undefined 的時候,Proxy get()的入參變成了 undefined,但 Proxy 第一個入參必須為物件

  • 需要對 obj 為 undefined 的時候進行特殊處理,為了能夠深層取值,所以使用一個空函式進行設定攔截,利用 apply trap 進行處理
    Vue3.0 響應式資料原理:ES6 Proxy

  • 我們理想中的應該是,如果屬性為 undefined 就返回 undefined,但仍要支援訪問下級屬性,而不是丟擲錯誤

  • 順著這個思路來的話,很明顯當屬性為 undefined 的時候也需要用 Proxy 進行特殊處理
    所以我們需要一個具有下面特性的 get() 方法

	getData(undefined)() === undefined; // true
	getData(undefined).xxx.yyy.zzz(); // undefined
  • 這裡完全不需要注意 get(undefined).xxx 是否為正確的值,因為想獲取值必須要執行才能拿到
  • 那麼只需要對所有 undefined 後面訪問的屬性都預設為 undefined 就好了,所以我們需要一個代理了 undefined 後的返回物件
  • 同時為了解決無限迴圈執行的問題,當第一次檢測到出現 undefined 的時候,停止執行
    Vue3.0 響應式資料原理:ES6 Proxy

六:日誌上報

Vue 3.0 的 Proxy & Object.defineProperty

Proxy

  • 劫持方式:代理整個物件,只需做一層代理就可以監聽同級結構下的所有屬性變化,包括新增屬性和刪除屬性
  • 本質Proxy 本質上屬於超程式設計非破壞性資料劫持,在原物件的基礎上進行了功能的衍生而又不影響原物件,符合鬆耦合高內聚的設計理念

Object.defineProperty

  • 劫持方式:只能劫持物件的屬性,不能直接代理物件
  • 流程:get中進行依賴收集,set資料時通知訂閱者更新
  • 存在的問題:雖然 Object.defineProperty 通過為屬性設定 getter/setter 能夠完成資料的響應式,但是它並不算是實現資料的響應式的完美方案,某些情況下需要對其進行修補或者hack,這也是它的缺陷,主要表現在兩個方面:
    • 無法檢測到物件屬性的新增或刪除
    • 不能監聽陣列的變化
      Vue3.0 響應式資料原理:ES6 Proxy

1. Object.defineProperty 無法監聽新增加的屬性

  • 解決方式:提供方法重新手動Observe,需要監聽的話使用 Vue.set() 重新設定新增屬性的響應式
    Vue3.0 響應式資料原理:ES6 Proxy

2. Object.defineProperty 無法一次性監聽物件所有屬性,如物件屬性的子屬性

  • 解決方式: 通過遞迴呼叫來實現子屬性響應式
    Vue3.0 響應式資料原理:ES6 Proxy

3. Object.defineProperty 無法響應陣列操作

  • 解決方式:通過遍歷和重寫Array陣列原型方法操作方法實現,但是也只限制在 push/pop/shift/unshift/splice/sort/reverse 這七個方法,其他陣列方法及陣列的使用則無法檢測到,也無法監聽陣列索引的變化和長度的變更
Vue3.0 響應式資料原理:ES6 Proxy

4. Proxy 攔截方式更多, Object.defineProperty 只有 get 和 set

5. Proxy 效能問題

6. Proxy 相容性差

  • Vue 3.0 中放棄了對於IE的支援(以為 Vue 3.0 中會對不相容的瀏覽器進行向下相容,但是經過檢視資料和原始碼發現尤大壓根沒做相容)
  • 目前並沒有一個完整支援 Proxy 所有攔截方法的 Polyfill 方案,有一個 google 編寫的 proxy-polyfill 也只支援了 get/set/apply/construct 四種攔截

多說一嘴 Decorator

  • ES7 中實現的 Decorator,相當於設計模式中的裝飾器模式。
  • 如果簡單地區分 ProxyDecorator 的使用場景,可以概括為:Proxy 的核心作用是控制外界對被代理者內部的訪問,Decorator 的核心作用是增強被裝飾者的功能。

寫在最後

  • 如果你覺得這篇文章對你有益,煩請點贊以及分享給更多需要的人!

快到碗裡來!百度校招還有HC!甩簡歷來!

極速直接內推【位元組跳動】&【百度】&【猿輔導】&【京東】

歡迎關注微信公眾號【全棧道路】,獲取更多科技相關知識及免費書籍。

更多好文

幾行程式碼教你解決微信生成海報及二維碼

冷門的HTML - tabindex 的作用

[萬字長文]百度和好未來面試經含答案

[前端面試]前端快取問題看這篇,讓面試官愛上你

記一次慘痛的Vue-cli + VueX + SSR經歷

[三分鐘小文]前端效能優化-HTML、CSS、JS部分

[三分鐘小文]前端效能優化-頁面載入速度優化

[三分鐘小文]前端效能優化-網路傳輸層優化

相關文章