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
的核心作用是增強被裝飾者的功能。
寫在最後
- 如果你覺得這篇文章對你有益,煩請點贊以及分享給更多需要的人!