? ? ? ? Vue的核心功能點之一是響應式:Vue 會自動跟蹤 JavaScript 狀態并在其發生變化時響應式地更新 DOM。
? ? ? ? 簡單的來說就是,頁面的渲染效果會隨著數據變化而變化,不用我們去手動操作DOM樹進行數據變化后的渲染。為了實現這一目的,我們最簡單的實現思路就是去監聽每一個對象中的值,在我們獲取或者更新這個值是插上一腳來操作DOM,此時,就可以通過函數去操作這個對象,以達到我們的目的。
vue2的響應式實現方法:defineProperty
? ? ? ? Object.defineProperty() 是 JavaScript 中的一個方法,用于直接在一個對象上定義一個新屬性,或者修改一個已經存在的屬性。它提供了精細的控制能力,可以定義屬性的特性(特性包括可枚舉、可配置、可寫性等),并可以定義 getter 和 setter,在屬性被訪問或者設置時執行自定義的行為。
? ? ? ? 這里vue2的思路則為通過該方法來遍歷對象,達到監聽對象屬性的目的,代碼如下:
const obj = {a: 1,b: 2,c: {d: 3,e: 4,}
} as any//觀察
const observer = (obj: any) => {for (const key in obj) {let temp = obj[key]if (typeof obj[key] === 'object') {observer(obj[key])} else {Object.defineProperty(obj, key, {get() {console.log('讀取', key)return temp},set(val) {console.log('修改', key)temp = val}})}}
}observer(obj)obj.a;// 讀取 a
obj.b = 3; // 修改 b
obj.c.d; // 讀取 c.dobj.f = 5 // 新添屬性 f 并修改 f
輸出結果:
?
? ? ? ? 這里我們可以發現, 我們用遞歸的方式來去對每個值進行了監聽,但是對應后面新增的對象屬性就沒有相對性的監聽效果,如果想進行監聽,就需要對這個屬性進行單獨的處理:
const obj = {a: 1,b: 2,c: {d: 3,e: 4,}
} as any//觀察
const observer = (obj: any) => {for (const key in obj) {let temp = obj[key]if (typeof obj[key] === 'object') {observer(obj[key])} else {Object.defineProperty(obj, key, {get() {console.log('讀取', key)return temp},set(val) {console.log('修改', key)temp = val}})}}
}const set = (obj: any, key: string, value: any) => {obj[key] = valuelet temp = obj[key]if (typeof value === 'object') {observer(value)} else {Object.defineProperty(obj, key, {get() {console.log('讀取', key)return temp},set(val) {console.log('修改', key)temp = val}})}
}observer(obj)obj.a;// 讀取 a
obj.b = 3; // 修改 b
obj.c.d; // 讀取 c.dset(obj, 'f', {g: 5
}) // 修改 fobj.f.g; // 讀取 f
輸出結果:
?vue3的響應式實現方法:Proxy
? ? ? ? Proxy 是 ES6 中新增的一個功能,它允許你在訪問一個對象之前定義自定義行為。通過 Proxy,你可以創建一個代理對象來包裹目標對象,并可以攔截并重定義該對象的各種操作,如屬性的讀取、賦值、刪除等操作,甚至可以自定義對象的行為。
? ? ? ?vue3使用的是Proxy來進行響應式操作的,這里就沒必要將對象的所有屬性進行遍歷,而是使用哪個屬性,則對哪個屬性進行監聽:
const obj ={a: 1,b: 2,c: {d: 3,e: 4,}
} as anyconst option = {get(target: any, key: any) {console.log('讀取', key)//判斷是否是對象if (typeof target[key] === 'object') {return new Proxy(target[key], option)}return target[key]},set(target: any, key: any, value: any) {console.log('設置', key, value)//判斷是否是對象if (typeof value === 'object') {target[key] = new Proxy(value, option)return true}return true},
}const proxy = new Proxy(obj, option)proxy.a = 10; // 設置 a 10
proxy.a; // 讀取 aproxy.c.f = 20; // 設置 c.f 20
proxy.c.f; // 讀取 c.fproxy.c; // 讀取 c
輸出結果:
總結
? ? ? ? vue2和vue3的響應式操作相比,vue2需要遍歷對象,且對于新的屬性無法直接進行監聽,而vue3使用代理對象就不會出現這個問題。