正如Vue文档中所述:
由于 JavaScript 的限制,Vue 无法检测到以下数组变动:1. 当你使用索引直接设置一项时,例如 vm.items[indexOfItem] = newValue 2. 当你修改数组长度时,例如 vm.items.length = newLength
首先,明白vue对于监听数据的变动是通过ES5的Object.defineProperty()来实现的,聊聊Vue的双向数据绑定 中已经说过Vue对于监测数组的变动是重写的数组的原型来达到目的的,原因是defineProperty不能检测数组的长度变化,准确来说是 通过改变数组的length而增加的长度是不能被监测到的 。
- Object.defineProperty()
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
| 1 | Object.defineProperty(obj, "a", { | 
- [[Writable]] 是否可写 
- [[Configurable]] 字面理解是表示属性是否可配置——能否修改属性;能否通过delete删除属性;能否把属性修改为访问器属性 
- [[Enumerable]] 能否通过for或for-in循环返回该属性 
- 关于数组length属性的特性
数组的length属性被初始化为:
| 1 | writable true | 
看到configurable为false也就是说,我们想要通过get/set方法来监听length属性是不可行的。
| 1 | var arr = [1, 2, 3]; | 
可以看到并没有打印出来新增加长度部分的键,因为length属性可以显式地去赋值的,但是新增加的长度(3和4)对应的键值是undefined,但是索引3和4的key是没有值的,上面的打印结果也可以说明defineProperty没办法对未知属性进行监听。
- 验证一下数组的几个内置的方法对索引的影响:1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82function defineReactive(data, key, val) { 
 Object.defineProperty(data, key, {
 enumerable: true,
 configurable: true,
 get: function defineGet() {
 console.log(`get key: ${key} val: ${val}`);
 return val;
 },
 set: function defineSet(newVal) {
 console.log(`set key: ${key} val: ${newVal}`);
 val = newVal;
 }
 })
 }
 function observe(data) {
 Object.keys(data).forEach(function(key) {
 defineReactive(data, key, data[key]);
 })
 }
 let test = [1, 2, 3];
 // 初始化
 observe(test);
 console.log(observe(test)); // 遍历数组索引并打印对应的值
 // get key: 0 val: 1
 // get key: 1 val: 2
 // get key: 2 val: 3
 test.push(4); // 新增了索引,但是可以看到新的索引并没有被observe
 console.log(observe(test));
 // get key: 0 val: 1
 // get key: 1 val: 2
 // get key: 2 val: 3
 test[3] = 5;
 console.log(observe(test)); // 修改新增的索引对应的值触发了对应的set方法,set了的新索引的值被observe时触发了get
 // set key: 3 val: 5
 // get key: 0 val: 1
 // get key: 1 val: 2
 // get key: 2 val: 3
 // get key: 3 val: 5
 test.pop(); // 弹出新的索引对应的值时触发了get方法
 console.log(observe(test));
 // get key: 3 val: 5
 // get key: 0 val: 1
 // get key: 1 val: 2
 // get key: 2 val: 3
 test[3] = 10; // 将刚刚弹出的索引重新赋值发现并没有触发set方法,所以已经删除的索引不会observe
 console.log(observe(test));
 // get key: 0 val: 1
 // get key: 1 val: 2
 // get key: 2 val: 3
 test.unshift(6); // 给数组的第一个位置添加新元素时,首先遍历除首元素外的所有索引和值并存放,然后重新对各索引赋值
 console.log(observe(test));
 // get key: 2 val: 3
 // get key: 1 val: 2
 // set key: 2 val: 2
 // get key: 0 val: 1
 // set key: 1 val: 1
 // set key: 0 val: 6
 // get key: 0 val: 6
 // get key: 1 val: 1
 // get key: 2 val: 2
 test.length = 20; // 可以看到数组的长度虽然更新了,但是新增的索引都是空并不会遍历数组去赋值索引,但是新增索引的值都为undefined
 console.log(test);
 // [ [Getter/Setter],
 // [Getter/Setter],
 // [Getter/Setter],
 // [Getter/Setter],
 // [Getter/Setter],
 // <15 empty items> ]
 console.log(observe(test));
 // get key: 0 val: 6
 // get key: 1 val: 1
 // get key: 2 val: 2
 // get key: 3 val: 3
 // get key: 4 val: 10
所以,Vue在文档中声明只能通过提供一些变异方法来对数组进行更新。
| 1 | // 源码目录:src/core/observer/array.js |