数组响应式的限制
Vue对数组响应式的处理可以分为两个方面来理解:
- 对数组项的以下7个操作方法做了重写,当数组使用下面方法改变了数组的值,Vue会派发更新通知依赖数组的watcher。
- push
- pop
- shift
- unshift
- splice
- sort
- reverse
- 数组项的值如果是对象,会对对象属性进行数据劫持实现响应式,完全走的就是对普通对象属性进行依赖收集,派发更新那一套流程。
Vue中不能检测以下数组的变动:
- 利用索引直接设置一个数组项;
- 修改数组的长度。
Vue中的数据响应式,实际上是通过对对象属性进行的数据劫持,而且无论是数组还是对象,响应式都是在初始化的时候完成的。Vue希望开发者可以提前声明所有的响应式属性,可以让响应式更可控。
数组项并不是一个对象的属性,在Vue中是不具有响应式的,在对这个数组项直接赋值的时候,数组值会改变,但并不会触发数组的setter方法。
⚠️如果这个数组项的值是一个对象,那这个对象里面的属性值是响应式的。
修改数组的长度,数组值会改变,但并不会触发数组的setter方法。新数组中只有数组项里还存在的对象属性是响应式的。
与其说Vue不能检测上面两种方式的变化,不如说Vue不想检测,尤大给的原因是性能代价和获得的用户体验收益不成正比。其实也好理解,对数据项直接赋值和修改数组的长度这两种方式都太不可控了。
数组响应式的实现
在对数组类型的数据进行响应式处理之前,先往数组上绑定了数原型上有的一些方法和属性。这些方法和属性中有7个方法是被Vue做了重写的。因为这7个方法会改变数组本身的值,而Vue根本就没有对数组项这个维度进行响应式处理,所以不会触发数组的setter方法。
为了处理这种情况,使数组在用的这7种方法改变值的时候会触发依赖更新,Vue将这几个方法做了一个重写。
其中pop、shift、sort、reverse四个方法,因为没有添加数组项,所以重写的步骤很简单,就是执行方法、手动派发更新和返回方法的结果值。
而push、unshift和splice方法因为添加了数据项,所以重写的步骤多了一项,简单来讲就是执行方法、将新增的数据项进行响应式处理、手动派发更新和返回方法的结果值。
往目标数组中绑定这些方法和属性时,根据浏览器是否支持__proto__,分为了两种处理方法。
- 支持__proto__:调用protoAugment方法通过原型式继承的方式,将目标数组的原型指向改造后的数组的实例,这个实例中既有数组的所有属性和方法,又有重写了的7个方法,这种方式是将方法和属性绑定在目标的原型链上。
- 不支持__proto__:调用copyAugment方法,通过def函数,遍历改造后的数组实例,将方法和属性挂在到目标数组的属性上。
总结
出于对性能的考虑,没有直接用Object.defineProperty去监听数组,但是需要知道Object.defineProperty是具备这个能力的。Vue2.x通过对常见的7种方法进行了重写,来实现对数组项的监听。