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進行記錄對象訪問
-
針對那些重度依賴資源,執行緩慢或是頻繁使用的方法或接口,統計它們的使用或是性能
-
可以記錄各種各樣的信息而不用修改應用程序的代碼或是阻塞代碼執行。並且只需要在這些代碼的基礎上稍事修改就可以記錄特性函數的執行性能
-
以上例子就是一個監聽函數執行的代理,可以將其進行擴展為打點函數
-
這里面
Proxy
的trap
為什么使用get
而不是apply
? 答案
四:普通函數與構造函數的兼容
- 構造函數調用沒有使用new關鍵字來調用的話,Class對象會直接拋出異常
- 使用
Proxy
進行封裝讓構造函數也能夠直接進行函數調用
五:深層取值判斷(看代碼)
-
需要解決的幾個問題
- 獲取數據進行攔截
xxx.xxx.xxx...
無論undefined
出現在哪里都不能報錯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
的時候,停止執行
六:日志上報
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
- ES6 Proxy 性能之我見
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
的核心作用是增強被裝飾者的功能。
寫在最后
- 如果你覺得這篇文章對你有益,煩請點贊以及分享給更多需要的人!