和Vue3和解的Day3--vue2的缺点

86 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情

说点题外话

这一篇我将从vue2和vue3双向绑定的底层原理的ProxyObject.defineProp方法入手,详细的说说为vue3为什么解决了vue2的缺点

说正文

image.png

vue2响应式的缺点?

1. Object.defineProperty 针对对象的缺点

我们都知道vue2的底层原理是基于Object.defineProperty的。整个方法的缺点也很明显,我们就先看看Object.defineProperty整个方法的缺点,从代码入手

    const foo = (obj, key, value) => {
      Object.defineProperty(obj, key, {
        get() {
          console.log(`key:${key}`);
          return value
        },
        set(newValue) {
          console.log(`${key}的值由${value}变成${newValue}`);
          value = newValue === value ? value : newValue
        }
      })
    }

    const data = { name: "小白" }
    
    Object.keys(data).map(key => foo(data, key, data[key]))
    console.log(data.name); 

此时打印结果为 key:name | 小白,说明只是触发了foo函数的get方法,如果我们将 data对象里面name属性的值呢?

    data.name = "小刚"
    console.log(data.name);

此时的输出结果是 name的值由小白变成小刚 | key:name | 小刚, 因为我们修改name属性的值,触发了foo函数的set方法,name属性的值也跟着更新了。

那么当我们在data对象上添加一个新的属性呢?

    data.age = 18
    console.log(data.age);

此时的打印结果为 18 , 说明既没有触发get方法也没有触发set方法。由此我们也可以得出一条vue2的缺点:Object.defineProperty 只对初始化对象的属性有作用,对于新增的鹅属性没有监听作用。官方也提出了使用$set方法来解决这个问题。

2. Object.defineProperty针对数组的缺点

我们总说在vue2中Object.defineProperty不能侦听数组下标的变化,那么这个方法是真的不能侦听数组下标吗?实际上Object.defineProperty这个方法是可以侦听数组下标变化的,但是vue2摒弃了这个特性,所以我们直接设置数组下标做不到响应式。

image.png

    const foo = (obj, key, value) => {
      Object.defineProperty(obj, key, {
        get() {
          console.log(`get=== key: ${key}=== value: ${value}`);
          return value
        },
        set(newValue) {
          console.log(`set=== key: ${key}=== value: ${newValue}`);
          value = newValue
        }
      })
    }

    const obsever = (data) => {
      Object.keys(data).map(key => foo(data, key, data[key]))
    }
    
    let arr = [1, 2, 3, 4]
    obsever(arr)
    arr[1]

当我们通过数组下标获取值结果: get=== key: 1=== value: 2, 会触发get方法

    arr[1] = 8

通过数组下标改变值结果是: set=== key: 1=== value: 8, 会触发set方法

push方法

arr.push(5)

我们会发现什么都没有输出,说明既没有触发get方法也没有触发set方法。Object.defineProperty是有监控数组下标变化的能力的,push是在原来数组的基础上新增,Object.defineProperty是监控原有数组下标的变化,所以push不会触发get方法和set方法。

unshift方法

    arr.unshift(0)

image.png

因为unit是在数组的前面添加值,所以需要将原来索引值为0,1,2,3的值取出来重新赋值执行unift之后的值。所以输出结果是上图。

那还有一个疑问,为什么原来value值是4的没有执行set方法呢,这就要再说一遍Object.defineProperty只能监控原有数组下标的变化。

这里我们对比对象来看,数组arr原数组是[1, 2, 3, 4], 所以只有索引为0,1,2,3的值的发生变化的时候才会触发我们写的observe方法。对于新增的索引就相当于对象中新增的属性一样,不会侦听新增的变化。

** pop方法**

    arr.pop()
    arr[3] = 8

输出的结果为get=== key: 3=== value: 4 , 因为执行pop方法将索引为3的值删除掉了,所以我们再去修改这个索引值是不会触发observe方法的。

3. 总结

Object.defineProperty是有监控数组下标的能力的,但是vue2放弃了这个特性

  • 通过索引获取或者修改对应的值时,可以触发get和set方法
  • 通过push和unshift新增新的索引,需要手动初始化才能执行get方法和set方法
  • 通过pop或者shift删除索引,会删除索引并更新索引,也会触发set方法和get方法。

Object.defineProperty在数组中的表现和对象中的表现是一样的,你可以将对象的key看作是数组的索引。

image.png

说再见

这一篇说了vue2底层原理Object.defineProperty的缺点,下一篇我们继续说vue3的优势以及是如何觉得vue2的痛点的。

难忘今宵

肚子饿为什么会咕咕叫?

当忽然闻到食物的香味、看到诱人的食物是,人的嘴里会自动分泌唾液,胃部也开始分泌胃酸,会快速消化掉胃里的食物,人们就会因为饥饿而肚子咕咕叫。