眾所周知,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 =https://www.cnblogs.com/wisewrong/p/ obj[key]
// 完成依賴添加后重置 target
Dep.target = null
}
update() {
// 獲得新值
this.value = https://www.cnblogs.com/wisewrong/p/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 = https://www.cnblogs.com/wisewrong/p/{ 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 = 'https://www.cnblogs.com/wisewrong/p/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 是一個可實體化的類
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/122056.html
標籤:JavaScript
上一篇:React 中 ref 的使用
下一篇:JS 實作計算器功能
