本文簡介
點贊 + 關注 + 收藏 = 學會了
首先,解答一下標題:Object.defineProperty 不能監聽原生陣列的變化,如需監聽陣列,要將陣列轉成物件,
在 Vue2 時是使用了 Object.defineProperty 監聽資料變化,但我查了下 檔案,發現 Object.defineProperty 是用來監聽物件指定屬性的變化,沒有看到可以監聽個陣列變化的,
但 Vue2 有的確能監聽到陣列某些方法改變了陣列的值,本文的目標就是解開這個結,
基礎用法
Object.defineProperty() 檔案
關于 Object.defineProperty() 的用法,可以看官方檔案,
基礎部分本文只做簡單的講解,
語法
Object.defineProperty(obj, prop, descriptor)
引數
obj要定義屬性的物件,prop要定義或修改的屬性的名稱或Symbol,descriptor要定義或修改的屬性描述符,
const data = https://www.cnblogs.com/k21vin/p/{}
let name ='雷猴'
Object.defineProperty(data, 'name', {
get() {
console.log('get')
return name
},
set(newVal) {
console.log('set')
name = newVal
}
})
console.log(data.name)
data.name = '鯊魚辣椒'
console.log(data.name)
console.log(name)
上面的代碼會輸出
get
雷猴
set
鯊魚辣椒
鯊魚辣椒
上面的意思是,如果你需要訪問 data.name ,那就回傳 name 的值,
如果你想設定 data.name ,那就會將你傳進來的值放到變數 name 里,
此時再訪問 data.name 或者 name ,都會回傳新賦予的值,
還有另一個基礎用法:“凍結”指定屬性
const data = https://www.cnblogs.com/k21vin/p/{}
Object.defineProperty(data,'name', {
value: '雷猴',
writable: false
})
data.name = '鯊魚辣椒'
delete data.name
console.log(data.name)
這個例子,把 data.name 凍結住了,不管你要修改還是要洗掉都不生效了,一旦訪問 data.name 都一律回傳 雷猴 ,
以上就是 Object.defineProperty 的基礎用法,
深度監聽
上面的例子是監聽基礎的物件,但如果物件里還包含物件,這種情況就可以使用遞回的方式,
遞回需要創建一個方法,然后判斷是否需要重復呼叫自身,
// 觸發更新視圖
function updateView() {
console.log('視圖更新')
}
// 重新定義屬性,監聽起來(核心)
function defineReactive(target, key, value) {
// 深度監聽
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue != value) {
// 深度監聽
observer(newValue)
// 設定新值
// 注意,value 一直在閉包中,此處設定完之后,再 get 時也是會獲取最新的值
value = https://www.cnblogs.com/k21vin/p/newValue
// 觸發視圖更新
updateView()
}
}
})
}
// 深度監聽
function observer(target) {
if (typeof target !=='object' || target === null) {
// 不是物件或陣列
return target
}
// 重新定義各個屬性(for in 也可以遍歷陣列)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 準備資料
const data = https://www.cnblogs.com/k21vin/p/{
name:'雷猴'
}
// 開始監聽
observer(data)
// 測驗1
data.name = {
lastName: '鯊魚辣椒'
}
// 測驗2
data.name.lastName = '蟑螂惡霸'
上面這個例子會輸出2次“視圖更新”,
我創建了一個 updateView 方法,該方法模擬更新 DOM (類似 Vue的操作),但我這里簡化成只是輸出 “視圖更新” ,因為這不是本文的重點,
測驗1 會觸發一次 “視圖更新” ;測驗2 也會觸發一次,
因為在 Object.defineProperty 的 set 里面我有呼叫了一次 observer(newValue) , observer 會判斷傳入的值是不是物件,如果是物件就再次呼叫 defineReactive 方法,
這樣可以模擬一個遞回的狀態,
以上就是 深度監聽 的原理,其實就是遞回,
但遞回有個不好的地方,就是如果物件層次很深,需要計算的量就很大,因為需要一次計算到底,
監聽陣列
陣列沒有 key ,只有 下標,所以如果需要監聽陣列的內容變化,就需要將陣列轉換成物件,并且還要模擬陣列的方法,
大概的思路和編碼流程順序如下:
- 判斷要監聽的資料是否為陣列
- 是陣列的情況,就將陣列模擬成一個物件
- 將陣列的方法名系結到新創建的物件中
- 將對應陣列原型的方法賦給自定義方法
代碼如下所示
// 觸發更新視圖
function updateView() {
console.log('視圖更新')
}
// 重新定義陣列原型
const oldArrayProperty = Array.prototype
// 創建新物件,原形指向 oldArrayProperty,再擴展新的方法不會影響原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function() {
updateView() // 觸發視圖更新
oldArrayProperty[methodName].call(this, ...arguments)
}
})
// 重新定義屬性,監聽起來(核心)
function defineReactive(target, key, value) {
// 深度監聽
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue != value) {
// 深度監聽
observer(newValue)
// 設定新值
// 注意,value 一直在閉包中,此處設定完之后,再 get 時也是會獲取最新的值
value = https://www.cnblogs.com/k21vin/p/newValue
// 觸發視圖更新
updateView()
}
}
})
}
// 監聽物件屬性(入口)
function observer(target) {
if (typeof target !=='object' || target === null) {
// 不是物件或陣列
return target
}
// 陣列的情況
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定義各個屬性(for in 也可以遍歷陣列)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 準備資料
const data = https://www.cnblogs.com/k21vin/p/{
nums: [10, 20, 30]
}
// 監聽資料
observer(data)
data.nums.push(4) // 監聽陣列
上面的代碼之所以沒有直接修改陣列的方法,如
Array.prototype.push = function() {
updateView()
...
}
因為這樣會污染原生 Array 的原型方法,這樣做會得不償失,
以上就是使用 Object.defineProperty 的方法,
如需監聽更多方法,可以在陣列 ['push', 'pop', 'shift', 'unshift', 'splice'] 中添加,
綜合代碼
// 深度監聽
function updateView() {
console.log('視圖更新')
}
// 重新定義陣列原型
const oldArrayProperty = Array.prototype
// 創建新物件,原形指向 oldArrayProperty,再擴展新的方法不會影響原型
const arrProto = Object.create(oldArrayProperty);
// arrProto.push = function () {}
// arrProto.pop = function() {}
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function() {
updateView() // 觸發視圖更新
oldArrayProperty[methodName].call(this, ...arguments)
}
})
// 重新定義屬性,監聽起來(核心)
function defineReactive(target, key, value) {
// 深度監聽
observer(value)
// 核心 API
// Object.defineProperty 不具備監聽陣列的能力
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue != value) {
// 深度監聽
observer(newValue)
// 設定新值
// 注意,value 一直在閉包中,此處設定完之后,再 get 時也是會獲取最新的值
value = https://www.cnblogs.com/k21vin/p/newValue
// 觸發視圖更新
updateView()
}
}
})
}
// 監聽物件屬性(入口)
function observer(target) {
if (typeof target !=='object' || target === null) {
// 不是物件或陣列
return target
}
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定義各個屬性(for in 也可以遍歷陣列)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
總結
上面的代碼主要是模擬 Vue 2 監聽資料變化,雖然好用,但也有缺點,
缺點
- 深度監聽,需要遞回到底,一次計算量大
- 無法監聽新增屬性/洗掉屬性(所以需要使用 Vue.set 和 Vue.delete)
- 無法原生監聽陣列,需要特殊處理
所以在 Vue 3 中,把 Object.defineProperty 改成 Proxy ,
但 Proxy 的缺點也很明顯,就是兼容性問題,所以需要根據你的專案來選擇用 Vue 2 還是 Vue 3 ,
推薦閱讀
??《『Three.js』起飛!》
??《Vue3 過10種組件通訊方式》
??《Fabric.js 從入門到目中無人》
??《Vite 搭建 Vue2 專案(Vue2 + vue-router + vuex)》
??《console.log也能插圖!》
點贊 + 關注 + 收藏 = 學會了
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/498883.html
標籤:JavaScript
上一篇:獲取元素的寬高的三種辦法
