vue2,vue3中Object.definedProperty和proxy解析,附部分实现代码

一、definedProperty

之前我就有一个很愚蠢的问题,对象中直接修改value值就可以改变对象的值,vue2中为什么还要使用Object.defindProperty呢,直接下面的代码就能实现了

// 定义一个对象
const obj = {
    name: "pan",
    age: 22
}

obj.name = "zhang"
console.log(obj); //{ name: 'zhang', age: 22 }

后来发现直接在对象中修改属性值可以实现单向绑定,但无法实现双向绑定。

在 Vue 2 中,如果直接在对象中修改属性值,Vue 是无法自动检测到属性值的变化并更新相关的视图。这是因为 Vue 2 使用了 Object.defineProperty() 来创建 getter 和 setter,并在 getter 和 setter 中进行依赖追踪和更新通知。只有通过 Vue 提供的特定方法(如 $set 或通过数组的变异方法)去修改对象的属性值,Vue 才能正确地进行依赖追踪和视图更新。

正题开始

vue2中怎么实现对象属性监听?

下面是一个小demo

const dog = {
    name: '小黄',
    age: 5
}

function listenAge(obj, key) {
    Object.defineProperty(obj, key, {
        get() {
            console.log(`属性${key}被访问了!`);
            return obj[key];
        },
    })
}
listenAge(dog, 'age')
console.log(dog.age);

上面的代码就可以实现访问dog.age时会执行defineProperty进行对象的监听,但是这里直接return obj[key]会出现死循环,会导致栈溢出,因为为对象定义getter和setter方法时,每次访问或者设置属性值的时候都会再去调用getter和setter方法。因此直接访问或者修改属性值的时候就会导致栈溢出。下面设置属性的时候同理

const dog = {
    name: '小黄',
    age: 5
}

function listenAge(obj, key) {
    Object.defineProperty(obj, key, {
        get() {
            console.log(`属性${key}被访问了!`);
            return obj[key];
        },
        set(newVal) {
            obj[key] = newVal
        }
    })
}
listenAge(dog, 'age')
console.log(dog.age);
dog.age = 6

那我们要怎么去修改呢?

我们可以不直接去访问和修改属性值,使用一个变量或者属性来存储实际的属性值,这样就可以避免栈溢出了。

const dog = {
    name: '小黄',
    age: 5,
    _age: 5
}

function listenAge(obj, key) {
    Object.defineProperty(obj, key, {
        get() {
            console.log(`属性${key}被访问了!`);
            return obj['_' + key];
        },
        set(newVal) {
            console.log(`属性${key}被设置新值:${newVal}`);
            obj['_' + key] = newVal
        }
    })
}
listenAge(dog, 'age')
console.log(dog.age); //5
dog.age = 6
console.log(dog.age); //6

如果第一次没有去设置新值直接去访问age的属性就会出现undefined,所以我们在初始化的时候多设置了一个_age:5,但是这种做法会使代码不优雅,因为时间关系,这里就没有去优化了,有好的解决办法也可以在评论区一起讨论。

这样我们就实现了Vue2中Object.defineProperty的简单demo

但是Object.defineProperty有一个致命的缺点,就是无法监听对象属性的新增和删除

可以使用this.$set和this.$delete解决,这个方法在项目中也经常使用,因为这里主要对比vue2和vue3的双向绑定,就不展开篇幅来讲,有兴趣的话我也会再出一篇文章详细讲讲。

二、proxy

es6新增proxy,Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了。

var proxy = new Proxy(target, handler)

target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))

handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为

下面一段代码实现proxy代理

function reactive(obj) {
    return new Proxy(obj, {
        get(target, key) {
            console.log(`对象属性${key}被访问了`);
            return target[key];
        },
        set(target, key, newVal) {
            console.log(`对象属性${key}被修改成:${newVal}`);
            target[key] = newVal;
            return true;
        }
    });
}

let obj = reactive({
    name: 'zhangsan',
    age: 18
});
console.log(obj); //初始化 reactive 对象并打印它时,并没有实际访问对象的属性,不会触发 get 拦截器
console.log(obj.name);//会触发get
obj.age = 13

值得注意的是我们在proxy下直接return target[key]

在 Proxy 的 get 拦截器中,直接返回 target[key] 不会导致死循环,因为在这个语境下,target[key] 实际上是在访问原始对象的属性值,而不是再次触发 get 拦截器。当你通过 obj[key] 访问属性时,Proxy 的 get 拦截器会被触发,但在拦截器内部返回 target[key] 时,它实际上是在访问原始对象 obj 中的属性值,并不会再次触发 get 拦截器。因此,这样的写法并不会导致死循环。而如果你在 get 拦截器中返回的是 return obj[key],那么就会形成递归,导致死循环。因为在这种情况下,又会触发 get 拦截器,导致无限循环。

在这里我们设置obj.sex = "男";也是会触发get和set的;

新增deleteProperty可以监听删除对象属性

function reactive(obj) {
    return new Proxy(obj, {
        get(target, key) {
            console.log(`对象属性${key}被访问了`);
            return target[key];
        },
        set(target, key, newVal) {
            console.log(`对象属性${key}被修改成:${newVal}`);
            target[key] = newVal;
            return true;
        },
        deleteProperty(target, proKey) {
            console.log(`删除对象属性${proKey}`);
            // 调用es6 Reflect操作对象的方法
            return Reflect.deleteProperty(target, proKey)
        }
    });
}

let obj = reactive({
    name: 'zhangsan',
    age: 18
});


delete obj.age
console.log(obj); // 打印删除后的对象

三、对比两者

Proxy可以监听增加和删除属性,还可以监听数组变化,defineProperty则不能,因此Vue2在实现响应式过程需要实现其他的方法辅助(如重写数组方法、增加额外setdelete方法)。

四、总结

vue中实现双向绑定考虑的因素非常多,肯定不止getter和setter这么简单有兴趣的可以看vue源码

vue2实现在src/core/observer下,链接https://github.com/vuejs/vue/tree/main/src/core/observer

vue3在core/package/reactivity/src下的reactives.ts和ref.ts,链接https://github.com/vuejs/core/tree/main/packages/reactivity

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值