vue2和vue3双向数据绑定的区别

相信有很多小伙伴早已经对vue的双向数据绑定有一定的了解了~,今天我们就来对比和重温一下vue2和vue3实现双向数据绑定的原理以及他们之前的一些细节问题。

首先我们先要明白响应式的底层设计原理:

vue的响应式原理设计三个重要对象:Observer,Watcher,Dep。

Observer对象:vue中的数据对象在初始化过程中转换为Observer对象。

Watcher对象:将模板和Observer对象结合在一起生成Watcher实例,Watcher是订阅者中的订阅者。

Dep对象:Watcher对象和Observer对象之间纽带,每一个Observer都有一个Dep实例,用来存储订阅者Watcher。

当属性变化会执行主题对象Observer的dep.notify方法, 这个方法会遍历订阅者Watcher列表向其发送消息, Watcher会执行run方法去更新视图。

 附上一张图帮助大家加深理解,上面这段文字,大家可以联系平时使用vue时的操作,更好理解

下面我们再来看看在vue2.0和vue3.0里面是分别是如何实现数据相应式的。

vue2.0

使用Object.defineProperty对象以及对象属性的劫持+发布订阅模式。

语法:

Object.defineproperty( object,‘ propName ’ ,descriptor);

object:要监听的目标对象

propName 要定义或修改的属性的名称。

descriptor:要定义或修改的属性描述符,操作详情。

// 基本使用
const obj={
   name: 'zhangsan',
}
Object.defineProperty(obj, 'name', {
    get() {
        console.log('触发get')
        return value
    },
    set(newValue) {
        if (newValue !== value) {
            console.log('触发set')
            value = newValue
            //updateView()
        }
    }
})
obj.name  // 触发get
obj.name='小小'  // 触发set

问题1:Object.defineproperty能监听到对象的新增删除属性吗?

不能,需要开发者主动调用相应的方法去更新 :Vue.set(),Vue.delete,由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。

问题2:Object.defineproperty能监听到数组的添加删除操作吗?

不能,依然可以Vue.set(),Vue.delete更新数据,看到这里,可能有些同学就会有疑问,那为啥我在项目中给数据使用push给数组添加一个数据,不需要调用set,页面能够更新响应呢,那是因为vue2.0使用数组重写的方法实现了数组的响应,7个方法分别为 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'。

问题3:如果我只是改变数组的索引的值,例如:vm.items[indexOfItem] = newValue真的不能被监听么?

答案是:能,Object.defineProperty能监听到,那为啥vue不给添加监听呢,因为性能对于对象而言,每一次的数据变更都会对对象的属性进行一次枚举,一般对象本身 的属性数量有限,所以对于遍历枚举等方式产生的性能损耗可以忽略不计,但是对于数组而言呢?数组包含的元素量是可能达到成千上万,假设对于每一次数组元素的更新都触发了枚举/遍历,其带来的性能损耗将与获得的用户体验不成正比,故vue无法检测数组的变动。

再者注意:Object.defineProperty()是深度监听,需要递归到底。一次性计算量很大。

vue3

vue3.0实现数据的响应式是通过 Proxy。

Object.defineProperty不同 的是Object.defineProperty只能劫持对象的属性,而Proxy是直接代理对象。因为proxy是对整个对象进行代理,所以可以监听对象某个属性值的变化,还可以监听对象属性的新增和删除,而且还可以监听数组。

proxy是ES6的新增的功能,可以用来定义对象中的操作

基本语法:

let pObj = new Proxy(target, handler);
// `target` 代表需要添加代理的对象
// `handler` 用来自定义对象中的操作

proxy是对整个对象进行代理,不关心对象里面具体有什么属性,而defineProperty只能响应首次渲染时候的属性,但是proxy的兼容性不好,IE无法兼容,vue2是兼容到了IE8,但是vue3使用了proxy说明放弃了对IE的兼容考虑。

下面我们分别手写Object.defineProperty,proxy监听一个对象的实现。

Object.defineProperty

// 触发更新视图
function updateView() {
    console.log('视图更新')
}
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
    nums: [10, 20, 30]
}
function defineReactive(target, key, value) {
    // 这里为啥要用defineReactive包裹,因为这个函数和get,set形成了闭包,
    // 就能保存value的值,不然的话,得定义一个全局变量去保存value
    observer(value) //深度监听
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if (newValue !== value) {
                observer(newValue) //这里设置observer是为了 data.age = { num: 1 },设置的值是个对象时进行监听
                value = newValue
                updateView()
            }

        }
    })
}
// 监听数组
const oldArrayPropoty = Array.prototype
const arrProto = Object.create(oldArrayPropoty)
const arrMethod = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
arrMethod.forEach(methodsName => {
    arrProto[methodsName] = function () {
        updateView()
        oldArrayPropoty[methodsName].call(this, ...arguments)
    }
})

function observer(target) {
    if (typeof target != 'object' || target == null) {
        //不是对象或者数组
        return target
    }
    if (Array.isArray(target)) {
        target.__proto__ = arrProto
        console.log(target)
    }
    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}
observer(data)
// data.name = "小明"
// data.info.address = '上海' // 深度监听
// data.age = { num: 1 }
// data.job='打杂' 对于新增属性是无法监控的
data.nums.push(4)

proxy:

const user = {
    name: '小明',
    age: 20,
    wife: {
        name: '小夏',
        age: 18
    }
}
//把目标对象变成代理对象
//参数1:user----->target目标对象
//参数2:handle---->处理器对象,用来监视数据以及数据操作
const proxyUser = new Proxy(user, {
    //获取目标对象的某个属性值
    get(target, props) {
        console.log('get方法调用了')
        return Reflect.get(target, props)  //需要用到Reflect将值反射出去,不然是拿不到值的,Reflect配合Proxy使用
    },
    // 修改目标对象的属性值
    set(target, props, value) {
        console.log('set方法调用了')
        return Reflect.set(target, props,value)
    },
    //删除目标对象的属性值,为目标对象添加新的属性
    deleteProperty(target, props) {
        console.log('delete方法调用了')
        return Reflect.deleteProperty(target, props)
    }
})

console.log(proxyUser.name)
//通过代理对象更新目标对象上的某个属性值
proxyUser.name='大明'
console.log(user)
delete proxyUser.name
console.log(user)
proxyUser.wife.name='小红'

建议想要真正了解 Object.defineProperty 和 proxy 是如何实现数据监听的小伙伴,可以自己手动实现下,对监听的过程加深理解

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值