当前位置:   article > 正文

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

vue2 object.define

一、definedProperty

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

  1. // 定义一个对象
  2. const obj = {
  3. name: "pan",
  4. age: 22
  5. }
  6. obj.name = "zhang"
  7. console.log(obj); //{ name: 'zhang', age: 22 }

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

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

正题开始

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

下面是一个小demo

  1. const dog = {
  2. name: '小黄',
  3. age: 5
  4. }
  5. function listenAge(obj, key) {
  6. Object.defineProperty(obj, key, {
  7. get() {
  8. console.log(`属性${key}被访问了!`);
  9. return obj[key];
  10. },
  11. })
  12. }
  13. listenAge(dog, 'age')
  14. console.log(dog.age);

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

  1. const dog = {
  2. name: '小黄',
  3. age: 5
  4. }
  5. function listenAge(obj, key) {
  6. Object.defineProperty(obj, key, {
  7. get() {
  8. console.log(`属性${key}被访问了!`);
  9. return obj[key];
  10. },
  11. set(newVal) {
  12. obj[key] = newVal
  13. }
  14. })
  15. }
  16. listenAge(dog, 'age')
  17. console.log(dog.age);
  18. dog.age = 6

那我们要怎么去修改呢?

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

  1. const dog = {
  2. name: '小黄',
  3. age: 5,
  4. _age: 5
  5. }
  6. function listenAge(obj, key) {
  7. Object.defineProperty(obj, key, {
  8. get() {
  9. console.log(`属性${key}被访问了!`);
  10. return obj['_' + key];
  11. },
  12. set(newVal) {
  13. console.log(`属性${key}被设置新值:${newVal}`);
  14. obj['_' + key] = newVal
  15. }
  16. })
  17. }
  18. listenAge(dog, 'age')
  19. console.log(dog.age); //5
  20. dog.age = 6
  21. 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代理

  1. function reactive(obj) {
  2. return new Proxy(obj, {
  3. get(target, key) {
  4. console.log(`对象属性${key}被访问了`);
  5. return target[key];
  6. },
  7. set(target, key, newVal) {
  8. console.log(`对象属性${key}被修改成:${newVal}`);
  9. target[key] = newVal;
  10. return true;
  11. }
  12. });
  13. }
  14. let obj = reactive({
  15. name: 'zhangsan',
  16. age: 18
  17. });
  18. console.log(obj); //初始化 reactive 对象并打印它时,并没有实际访问对象的属性,不会触发 get 拦截器
  19. console.log(obj.name);//会触发get
  20. 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可以监听删除对象属性

  1. function reactive(obj) {
  2. return new Proxy(obj, {
  3. get(target, key) {
  4. console.log(`对象属性${key}被访问了`);
  5. return target[key];
  6. },
  7. set(target, key, newVal) {
  8. console.log(`对象属性${key}被修改成:${newVal}`);
  9. target[key] = newVal;
  10. return true;
  11. },
  12. deleteProperty(target, proKey) {
  13. console.log(`删除对象属性${proKey}`);
  14. // 调用es6 Reflect操作对象的方法
  15. return Reflect.deleteProperty(target, proKey)
  16. }
  17. });
  18. }
  19. let obj = reactive({
  20. name: 'zhangsan',
  21. age: 18
  22. });
  23. delete obj.age
  24. 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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/259007?site
推荐阅读
相关标签
  

闽ICP备14008679号