正如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 |