vue2和vue3的數(shù)據(jù)綁定原理
作者:yingmhd
來源:SegmentFault 思否社區(qū)
VUE 雙向綁定原理
首先Vue是個類
new Vue({
data(){},
methods: {}
})
實例化Vue的時候要傳入data,Vue類內(nèi)部對data進行劫持轉(zhuǎn)換成getter/setter,如何劫持
vue2 數(shù)據(jù)劫持
核心方法: Object.defineProperty
H5方法,所以不兼容IE8以下
let obj = {},
value = 1
Object.defineProperty(obj,'a',{
get() {
console.log('這里監(jiān)聽到了數(shù)據(jù)獲取')
return value
},
set(newValue, value) {
if(newValue !== value) {
value = newValue
console.log('這里監(jiān)聽到了數(shù)據(jù)更改')
}
}
})
console.log(obj.a) // 這里監(jiān)聽到了數(shù)據(jù)獲取 1
obj.a = 2 // 這里監(jiān)聽到了數(shù)據(jù)更改
所以再初始化Vue時,對data進行了劫持,每個屬性都通過Object.defineProperty變成了getter/setter,一旦數(shù)據(jù)發(fā)生改變,就會觸發(fā)set,然后去更新view
let data = {
name: 'nike',
info: {
age: 21
}
}
Object.keys(data).forEach(key=>{
defineProperty(data, key, data[key])
})
function defineProperty(target, key, value) {
Object.defineProperty(target,key,{
get() {
console.log('這里監(jiān)聽到了數(shù)據(jù)獲取')
return value
},
set(newValue, value) {
if(newValue !== value) {
value = newValue
console.log('這里監(jiān)聽到了數(shù)據(jù)更改')
}
}
})
}
data.name = 'tom' // 這里監(jiān)聽到了數(shù)據(jù)更改
data.info.age = 22 // 這里監(jiān)聽到了數(shù)據(jù)獲取(這里沒有觸發(fā)更改,get和set相對立,總要觸發(fā)一個)
data.info = {age:22} // 這里監(jiān)聽到了數(shù)據(jù)更改
至于data.info.age = 22為什么沒有觸發(fā)set呢,因為上面的邏輯僅僅是對data下面的一層進行了劫持,而再往下的改變是監(jiān)聽不到的,所以就引出了兩外一個東西
Watch watch: {
info: {
handler(){},
deep: true
}
}此處的 deep表示深度監(jiān)聽,這樣就會對該屬性遞歸遍歷并逐一劫持,類似于深拷貝vue.$set
從字面意思看,就是手動觸發(fā)set
Object.defineProperty有一個bug,就是無法監(jiān)聽數(shù)組(因為數(shù)組沒key)
let data = {
name: [],
}
Object.keys(data).forEach(key=>{
defineProperty(data, key, data[key])
})
function defineProperty(target, key, value) {
Object.defineProperty(target,key,{
get() {
console.log('這里監(jiān)聽到了數(shù)據(jù)獲取')
return value
},
set(newValue, value) {
if(newValue !== value) {
value = newValue
console.log('這里監(jiān)聽到了數(shù)據(jù)更改')
}
}
})
}
data.name.push('nike') // 這里監(jiān)聽到了數(shù)據(jù)獲取
為了解決這個問題,Vue對數(shù)組的方法進行了重寫
// 重寫push
let oldPush = Array.prototype.push
Array.prototype.push = function() {
console.log('這里觸發(fā)view更新')
oldPush.call(this,...arguments)
}
vue3 數(shù)據(jù)劫持
很明顯,Object.defineProperty有一些缺陷,不僅要遍歷data逐個劫持,還不能監(jiān)聽到數(shù)組的改變,所以VUE3使用了ES6的ProxyProxy字面理解代理,就跟經(jīng)紀人一樣,一旦與某個明星data綁定,那么這個明星想干嘛就得先通過代理
let data = {
msg: {
a: 10
},
arr: [1, 2, 3]
}
let handler = {
get(target, key) {
// 懶監(jiān)聽,去獲取的時候才監(jiān)聽對象里面的對象,而不是直接遞歸循環(huán)監(jiān)聽
console.log('獲取key: ' + key)
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key)
},
set(target, key, value) {
let oldValue = target[key]
console.log('更新key: ' + key)
if (oldValue !== value) {
// 通知view更新
}
return Reflect.set(target, key, value)
}
}
let proxy = new Proxy(data, handler)
proxy.arr.push(4)
輸出結(jié)果
為什么每次都有length,其實Proxy的監(jiān)聽數(shù)組實現(xiàn)是把數(shù)組變成了一個類數(shù)組對象而已
let arr = {
'0': 1,
'1': 2,
length: 2
}
Proxy除了get,set還有deleteProperty/apply/getOwnPropertyDescriptor等等12個方法,恰好與Reflect對應,所以在這些方法里面可以實現(xiàn)攔截器
set(target, key, value) {
if(key[0] === '_') {
throw new Error('這是私有變量,不能更改')
}
return Reflect.set(target, key, value)
}


