從 defineProperty 到 Proxy


眾所周知,Vue 2.x 的數據綁定是通過 defineProperty。而在 Vue 3.x 的設計中,數據綁定是通過 Proxy 實現的,這兩者到底有何異同?

 

一、definePropety

defineProperty 是 Object 的一個方法,可以在對象上新增或編輯某個屬性,可編輯的內容除了屬性值 value 之外,還有該屬性的描述信息

Object.defineProperty(obj, prop, descriptor)

該方法接收三個參數,分別是目標對象 obj,被編輯的屬性名 prop,以及該屬性的描述 descriptor

需要注意的是,只能在 Object 構造器對象使用該方法,實例化的 object 類型是沒有該方法的

 

 

1. 基礎描述符

  • configurable當該鍵值為 true 時,該屬性的描述符才能夠被改變,同時該屬性也能從對應的對象上被刪除。默認為 false。

當該描述符為 false 的時候,其它的描述符一旦定義,就無法再更改,且該屬性無法被 delete 刪除

 

  • enumerable當該鍵值為 true 時,該屬性才會出現在對象的枚舉屬性中。默認為 false。

當 enumerable 為 false 時,Objcet.keys() 和 for...in 都無法獲取到被定義的屬性

但 Reflect.ownKeys() 可以...

  

2. 數據描述符

  • value屬性值。可以是任何有效的 JavaScript 值 (數值,對象,函數等)。默認為 undefined。
  • writable當該鍵值為 true 時,屬性的值(即 value)才能被賦值運算符改變。 默認為 false。

 

3. 存取描述符

  • get:該屬性的 getter 函數,訪問該屬性時候會調用該函數,其返回值會被用作 value,默認為 undefined。

該函數沒有入參,但是可以使用 this 對象,只是這個 this 不一定是源對象 obj

 

  • set: 該屬性的 setter 函數,當屬性值被修改時,會調用此函數,默認為 undefined。

該方法接受一個參數,即被賦予的新值,同時會傳入賦值時的 this 對象

 

⚠️注意:數據描述符和存取描述符不可同時存在!

  

4. Vue 2.x 響應式原理

在 Vue 2.x 中其實就是在觀察者模式中使用上面提到的 get 和 set 實現的數據綁定

首先實現依賴收集和 Watcher

// 通過 Dep 解耦屬性的依賴和更新操作
class Dep { constructor() { this.subs = [] } // 添加依賴
 addSub(sub) { this.subs.push(sub) } // 更新
 notify() { this.subs.forEach(sub => { sub.update() }) } } // 全局屬性,通過該屬性配置 Watcher
Dep.target = null class Watcher { constructor(obj, key, up) { // 手動觸發 getter 以添加監聽
    Dep.target = this
    this.up = up this.obj = obj this.key = key this.value = obj[key] // 完成依賴添加后重置 target
    Dep.target = null } update() { // 獲得新值
    this.value = this.obj[this.key] // 調用 update 方法更新 Dom
    this.up(this.value) } }

然后通過 defineProperty 來實現響應

function observe(obj) { if (!obj || typeof obj !== 'object') { return } Object.keys(obj).forEach(key => { defineReactive(obj, key, obj[key]) }) } function defineReactive(obj, key, val) { // 遞歸子屬性
 observe(val) let dp = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { // 將 Watcher 添加到訂閱
      if (Dep.target) { dp.addSub(Dep.target) } return val }, set(newVal) { val = newVal // 執行 watcher 的 update 方法
 dp.notify() } }) }

完成之后,通過 observe 遍歷對象,然后實例化 Watcher,手動觸發一次 getter 完成數據綁定

const data = { name: '' } observe(data) function update(value) { document.body.innerHTML = `<div>${value}</div>`
} // 模擬解析到 `{{name}}` 觸發的操作
new Watcher(data, 'name', update) data.name = 'Wise.Wrong'

這部分代碼參考自掘金小冊《前端面試之道》

 

二、Proxy

以 Object.defineProperty() 實現的響應式有兩個問題:

1. 給對象新增屬性並不會更新 DOM;

2. 以索引的方式修改數組也不會觸發 DOM 的更新。

最終 Vue 是通過重寫函數的方式解決了這兩個問題,但對於數組的數據綁定依然有瑕疵

而這些問題,對於 Proxy 來說都不是問題

 

1. 簡介

const p = new Proxy(target, handler)

這里的目標對象 target 可以是任何類型的對象,包括原生數組,函數,甚至另一個 Proxy

而對應的處理器對象 handler 包含很多的 trap 方法,這些 trap 方法會在 Proxy 對象執行對應操作時觸發

下面會介紹幾個常用的方法

getPrototypeOf() Object.getPrototypeOf 方法對應的鈎子函數
setPrototypeOf() Object.setPrototypeOf 方法對應的鈎子函數
defineProperty() Object.defineProperty 方法對應的鈎子函數
has() in 操作符對應的鈎子函數
deleteProperty() delete 操作符對應的鈎子函數
apply() 函數被調用時的鈎子函數
construct() new 操作符對應的鈎子函數
get() 屬性讀取操作的鈎子函數
set() 屬性被修改時的鈎子函數

鈎子函數會在對 Proxy 對象執行相應操作的時候觸發

 

2. 鈎子函數

以 set 和 get 為例

function update(value = 'wise.wrong') { console.log('update'); document.body.innerHTML = value; }; const data = ['who', 'am', 'i']; const subject = new Proxy(data, { get: function(obj, prop) { return obj[prop]; }, set: function(obj, prop, value) { update(value); obj[prop] = value; } });

上面的目標的對象是一個數組,然后實例化 Proxy 的時候添加了 set 的鈎子函數

當 Proxy 對象 subject 被修改的時候,會執行 update 方法

基於這些鈎子函數,就可以參考上面 Object.defineProperty() 的思路實現數據綁定了,而且還不會有上面的遺留問題

 

3. 和 defineProperty 的區別

defineProperty 需要針對具體的 key 設置 getter 和 setter

Object.defineProperty(obj, prop, descriptor)

以至於 Vue 2.x 在初始化的時候,需要遞歸遍歷對象的子屬性,挨個兒掛載 setter

這也導致了無法直接通過 defineProperty 實現在對象中新增屬性時更新 DOM

但 Proxy 是針對整個對象的代理,不會關心具體的 key

而且 Proxy 的目標對象並沒有類型限制,除了 Object 之外,還天然支持 Array、Function 的代理

此外 Proxy 還不僅僅支持 getter 和 setter,上面提到的鈎子函數 ,在特定的場景下會發揮出應有的作用

所以 Proxy 比 Object.defineProperty()  的層次更高,畢竟 defineProperty 只是一個方法,而 Proxy 是一個可實例化的類

 


免責聲明!

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



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