Vue响应式踩坑

Posted on Aug 17, 2019

Vue响应式原理

Vue实现的响应式系统是基于Object.definedProperty()(还有一个类似的方法Object.definedProperty(),可以同时定义多个属性)的。

Object.definedProperty()方法可以对一个对象中的属性定义setget方法。

  1. get方法在访问该属性时触发,返回值即访问该属性得到的值
  2. set方法在修改该属性值时触发

Vue的实现方法是用Object.definedProperty将作为Vue中data选项的JavaScript对象里面定义的所有属性都转化getter/setter。 我们可以把它当作在原来的JS对象里定义的所有属性外面包了一层壳(是属性外面不是JS对象外面),这层壳可以监听到外部对对象属性的访问(get)和设置(set)。

关于getter/setter

在mdn文档中对其的解释如下:

一个getter是一个获取某个特定属性的值的方法。一个setter是一个设定某个属性的值的方法。 你可以为预定义的或用户定义的对象定义gettersetter以支持新增的属性。 定义gettersetter的语法采用对象字面量语法。

需要注意的是,如果在一个对象里已经用get关键字定义了一个getter那么在同一个对象里就不能再出现同名的属性, 同名的属性和getter/setter无法共存。

1// 错误写法
2let obj = {
3  getterr: 'getter',  // 同名属性
4  get getterr() {     // 同名getter
5    return 'getter'; 
6  }
7};

从不能有同名的属性和getter/setter这个特性我们可以看出上文的转化这个词的必要性,当然这Vue文档中的原文。 通过转化我们大致可以想象出Vue对响应式JS对象的处理过程。

  1. 将data选项中的原始JS对象里面的所有属性重命名。将其转化为内部属性,比如inner -> _inner
  2. 定义同属性名的gettersetter,因为之前的属性已经重命名了,所以这一步不会产生属性名的冲突。

示例代码:

 1// 对一个对象简单的转化
 2// 将inner属性转化为`getter/setter`
 3let obj = {
 4  inner: true
 5};
 6
 7let keys = Object.keys(obj);
 8for (let i = 0, len = keys.length; i < len; i++) {
 9  let v = obj[keys[i]];
10  obj['_' + keys[i]] = v;
11  Object.defineProperty(obj, keys[i], {
12    get: () => {
13      return this._inner;
14    },
15    set: (newV) => {
16      this._inner = newV;
17    }
18})
19}

Vue用Object.definedPropoty()实现响应式系统存在的问题

Object.definedPropoty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。

这个方法只能操作JS对象里的属性,对其修改或创建。同样的,我们也只能将其已定义的属性转化为上文提到的getter/setter

换句话说,我们只能观测已定义的属性的改变,无法观测到JS对象里是否添加或删除里某个属性。这一点也在Vue的文档中提到了。 并且也给出了解决方案。所以Vue也建议开发者将需要用到的属性预先定义在data选项中, 即使是先将值定义为空。