背景

vue2中,我们是无法实现数组元素修改的响应式的:
image.png
像这样,我们直接通过下标修改数组元素,是无法触发试图更新的,直接通过下标修改数组元素其实在**vue2**中并不是响应式的。


那么问题来了,为什么对象通过**key**索引修改属性值会触发视图更新,但是数组通过数字索引修改数组元素不会触发视图更新呢,照理说,数组是特殊化的对象,数字索引也应该对应对象的**key**索引,那为什么他们会存在不同的情况呢?

答疑

为什么对象通过key索引修改属性会触发视图更新?

因为在源码中,对象的每个属性其实都通过**defineReactive**函数进行了改造,改造成了访问器属性,从而使得对象属性具有了响应式。
image.png
image.png

为什么数组不行呢?

因为数组并没有进行**walk**操作。也就是说,你对通过数字索引对数组某个元素进行修改,在**vue2**中并不是一个响应式的操作。

为什么不对数组元素进行walk操作

第一时间,我想到的是Object.definePropertyAPI可能不能对数组进行改造。但是细想一下,数组本就是对象的一种,也是键值对的形式,为什么不能使用Object.defineProperty进行改造呢?
实践是检验真理的唯一标准,我们尝试一下是否能通过Object.defineProperty属性对数组进行改造:

  1. var arr = [1];
  2. let value = arr[0];
  3. Object.defineProperty(arr, '0', {
  4. get() {
  5. console.log("get");
  6. return value;
  7. },
  8. set (val) {
  9. console.log("set");
  10. value = val;
  11. }
  12. })

我们来测试一下是否能够拦截到数组的get操作和set操作:
image.png
说明确实我们可以拦截到数组通过下标索引访问数组元素和修改数组元素的行为。
那么问题来了,Vuejs为什么没有这样做呢?

为什么vue没有对数组元素使用defineReative方法?

首先我思考的是,可能是由于数据量的问题,毕竟我知道,对象的属性总的来说还是有限的,一般最多几十个,但是几十个元素的数组却是很常见,有的数组甚至成百上千个。如果遍历改造每一个数组元素的话,可能会十分的消耗性能。后面我转念一想,为什么数组元素是对象的情况下,那么多数组元素对象都能被改造,那么说明其实改造数组消耗的性能是可以接受的。
后来,我想从网上找到答案,我发现在Vue.js[issue#8562](https://github.com/vuejs/vue/issues/8562)中其实有这方面的解答:
image.png
尤玉溪说,是因为性能问题而不是实现问题没有这样做。那我更加好奇是什么性能问题导致vue放弃了这部分的响应式实现。经过一系列的思考,我好像大概能知道他说的性能问题以及付出和回报不成正比的问题所在了。

其实,这应该源于数组的特性,数组是有序的,所以数组的索引之间是存在一定顺序的。但是对象的键值对是无序的,正是由于数组的有序性所以导致当你插入一个元素的时候,就会导致索引的移动。 举个例子,比如说,我们从数组的头部插入一个元素,那么之前数组的所有元素对应的索引都要移动(+1)。这就会导致数组所有元素对应的**get****set**函数会都要执行一次,这就感觉这次操作仅仅是**unshift**一个数组元素,但是他其实产生了很多的副作用,这可能就是yxx说的性能代价和用户体验不成正比的原因。

所以,**vuejs**放弃了这方面的一些开发体验,需要开发者做一些妥协。比如说,我们使用splice方法,来代替我们通过数组的数字索引来修改数组元素。

  1. this.list[0] = 22;//废弃
  2. this.list.splice(0,1,22);//推荐