vue2和vue3的数据绑定原理

SegmentFault

共 3450字,需浏览 7分钟

 · 2021-03-25


作者:yingmhd

来源:SegmentFault 思否社区



VUE 双向绑定原理

首先Vue是个类

new Vue({
data(){},
methods: {}
})

实例化Vue的时候要传入dataVue类内部对data进行劫持转换成getter/setter,如何劫持

vue2 数据劫持



核心方法: Object.defineProperty

H5方法,所以不兼容IE8以下

let obj = {},
value = 1
Object.defineProperty(obj,'a',{
get() {
console.log('这里监听到了数据获取')
return value
},
set(newValue, value) {
if(newValue !== value) {
value = newValue
console.log('这里监听到了数据更改')
}
}
})
console.log(obj.a) // 这里监听到了数据获取 1
obj.a = 2 // 这里监听到了数据更改

所以再初始化Vue时,对data进行了劫持,每个属性都通过Object.defineProperty变成了getter/setter,一旦数据发生改变,就会触发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('这里监听到了数据获取')
return value
},
set(newValue, value) {
if(newValue !== value) {
value = newValue
console.log('这里监听到了数据更改')
}
}
})
}
data.name = 'tom' // 这里监听到了数据更改
data.info.age = 22 // 这里监听到了数据获取(这里没有触发更改,get和set相对立,总要触发一个)
data.info = {age:22} // 这里监听到了数据更改

至于data.info.age = 22为什么没有触发set呢,因为上面的逻辑仅仅是对data下面的一层进行了劫持,而再往下的改变是监听不到的,所以就引出了两外一个东西

  1. Watch
    watch: {
    info: {
    handler(){},
    deep: true
    }
    }
    此处的deep表示深度监听,这样就会对该属性递归遍历并逐一劫持,类似于深拷贝
  2. vue.$set
    从字面意思看,就是手动触发
    set

Object.defineProperty有一个bug,就是无法监听数组(因为数组没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('这里监听到了数据获取')
return value
},
set(newValue, value) {
if(newValue !== value) {
value = newValue
console.log('这里监听到了数据更改')
}
}
})
}
data.name.push('nike') // 这里监听到了数据获取

为了解决这个问题,Vue对数组的方法进行了重写

// 重写push
let oldPush = Array.prototype.push
Array.prototype.push = function() {
console.log('这里触发view更新')
oldPush.call(this,...arguments)
}

vue3 数据劫持



很明显,Object.defineProperty有一些缺陷,不仅要遍历data逐个劫持,还不能监听到数组的改变,所以VUE3使用了ES6Proxy
Proxy字面理解代理,就跟经纪人一样,一旦与某个明星data绑定,那么这个明星想干嘛就得先通过代理

let data = {
msg: {
a: 10
},
arr: [1, 2, 3]
}
let handler = {
get(target, key) {
// 懒监听,去获取的时候才监听对象里面的对象,而不是直接递归循环监听
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)

输出结果

为什么每次都有length,其实Proxy的监听数组实现是把数组变成了一个类数组对象而已

let arr = {
'0': 1,
'1': 2,
length: 2
}

Proxy除了get,set还有deleteProperty/apply/getOwnPropertyDescriptor等等12个方法,恰好与Reflect对应,所以在这些方法里面可以实现拦截器

set(target, key, value) {
if(key[0] === '_') {
throw new Error('这是私有变量,不能更改')
}
return Reflect.set(target, key, value)
}




点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,扫描下方”二维码“或在“公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~

- END -


浏览 7
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报