Vue3.0 響應式數據原理:ES6 Proxy


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

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

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

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

  • 注意: 要是 Proxy 代理起作用,必須針對 Proxy 的實例進行操作,而不是針對目標對象進行操作

二:數據校驗(看代碼)

  • 利用 Proxy 代理進行簡單數據校驗
  • 校驗邏輯直接加在代理處理函數中過於繁重,我們可以把校驗模塊直接抽離出來,只需要去處理校驗的邏輯,代理層面后續不需要改動

三:利用proxy進行記錄對象訪問

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

  • 可以記錄各種各樣的信息而不用修改應用程序的代碼或是阻塞代碼執行。並且只需要在這些代碼的基礎上稍事修改就可以記錄特性函數的執行性能

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

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

四:普通函數與構造函數的兼容

  • 構造函數調用沒有使用new關鍵字來調用的話,Class對象會直接拋出異常
  • 使用 Proxy 進行封裝讓構造函數也能夠直接進行函數調用

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

  • 需要解決的幾個問題

    1. 獲取數據進行攔截
    2. xxx.xxx.xxx...無論 undefined 出現在哪里都不能報錯
    3. Proxyget() 傳入的參數必須是對象
  • 傳統方式深層取值繁瑣,利用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 的時候,停止執行

六:日志上報

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 性能問題

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部分

[三分鍾小文]前端性能優化-頁面加載速度優化

[三分鍾小文]前端性能優化-網絡傳輸層優化


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM