之前学习了关于Vue的响应式数据的原理: Vue 的双向绑定原理及手把手实现。它的原理其实就是通过Object.defineProperty控制    getter和setter结合发布订阅者模式完成响应式设计的,
1. 但是这种数据劫持对数组有什么影响呢?
这种递归方式无论对于数组还是对象都进行了观测。但是我们的数组有成千上万个元素,每一个元素下标都添加get 和set.这样对于性能来说代价太大了。那么Object.property只用来劫持对象。
2. Object.property这种劫持方式有什么缺点呢?
对于新增的或者删除的属性是无法被检测到的,只有对象本身存在的属性才会被劫持。
对于数组来说也是一样,新增加的元素和删除的元素无法对他们的下表进行劫持。
 
根据以上两点可以得出这就是为什么Vue官方说如下的话:
由于JavaScript的限制,Vue无法检测到以下数组的变动:
- 当你使用索引设置一项时;如:vm.items[indexOfItem] = newValue
- 当修改数组的长度时;如:vm.items.length =  newLength
我们举个例子:
之前的文章  Vue 的双向绑定原理及手把手实现写好了Observer。 我们来做一个实验:
| 12
 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
 
 | <html><header></header>
 <body>  <div id="app"></div></body>
 <script src="./js/observer.js"></script>
 <script src="./js/index.js"></script>
 <script src="./js/compile.js"></script>
 <script src="./js/watcher.js"></script>
 <script>
 const vm = new MyVue({
 el: "#app",
 data: {
 name: "舒丽琦",
 list: ["1", "2", "3"]
 }
 })
 
 vm.name = " 小小舒"
 
 vm.name;
 
 
 vm.list.push("4");
 
 vm.list[0];
 console.log(vm.list);
 </script>
 </html>
 
 | 
关上面的代码的代码可到  Vue 的双向绑定原理及手把手实现找到
我们看看实验的结果:
 
从结果可以看出:data 中的 name, list 均发生了变化; name发生了变化能检测到,但是是list发生变化无法检测到。这是为什么呢?
原来操作数组的方法是在 Array.prototype上的; 挂在在Array.prototype上的方法并不能触发属性的getter和setter。
Vue 检测数组变化-重写数组
那解决这个问题的办法是什么呢?Vue2.x使用的是将数组常用的方法进行重写。
基本的思路是之前我们调用数组的常用的方法的时候(如push·);我们是从 Array.prototype上面寻找这个方法,现在我们改成一个空对象 {} 继承  Array.prototype,然后给 空对象添加 push方法;
| 12
 3
 4
 
 | {push: function() {}
 
 }
 
 | 
这样,我们调用常用的办法,实际上就是在调用 这个空对象的方法。因为常用的方法使我们自己重写的,肯定就知道当前操作是什么,新数据是什么等等,就可以做到检测数组的变化了。
既然是这样, 那么我们的observer是需要区分当前需要监听的数据是对象还是数组,如果是数组,则改变数组的原型链,只有改变了原型链才能改变调用数组常用的方法如push方法时,是调用我们自己的设置的常用的方法的。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | function Observer(data) {const _this = this;
 
 if (!data || typeof data !== "object") {
 return;
 }
 if (Array.isArray(data)) {
 data.__proto__ = arrayMethods;
 } else {
 
 Object.keys(data).forEach(function (key) {
 defineReactive(data, key, data[key]);
 })
 }
 }
 
 | 
上面代码的arrayMethods就是我们所说的空对象。它里面添加数组常用的方法如:push等
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | 
 const oldArrayProperty = Array.prototype;
 
 
 const arrayMethodObj = Object.create(oldArrayProperty);
 
 
 const arrayMethods =  ["push", "shift", "unshift", "pop","reverse", "sort", "splice"];
 arrayMethods.forEach(method => {
 arrayMethodObj[method] = function(...arg) {
 
 const result = oldArrayProperty[method].apply(this, arg);
 console.log(`数组有变化了,方法:${method}, 新增加的值为: ${inserted}`);
 return result;
 }
 })
 
 
 | 
下面我们实现对新增属性的监听。基本的思路:
| 12
 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
 
 | 
 const oldArrayProperty = Array.prototype;
 
 
 const arrayMethodObj = Object.create(oldArrayProperty);
 
 
 const arrayMethods =  ["push", "shift", "unshift", "pop","reverse", "sort", "splice"];
 arrayMethods.forEach(method => {
 arrayMethodObj[method] = function(...arg) {
 
 const result = oldArrayProperty[method].apply(this, arg);
 
 
 let inserted;
 switch(method) {
 case "push":
 case "unshift":
 inserted = arg;
 break
 case "splice":
 
 inserted = arg.slice(2);
 break
 default:
 break;
 }
 if (inserted) {
 
 observerArray(inserted);
 }
 console.log(`数组有变化了,方法:${method}, 新增加的值为: ${inserted}`);
 
 
 return result;
 }
 })
 
 
 | 
上面代码中有一个 observeArray方法去监听新增加的数组的元素。我们看看 observeArray方法。
| 12
 3
 4
 5
 6
 7
 
 | function observeArray(items) {
 
 for (var i = 0, l = items.length; i < l; i++) {
 observer(items[i]);
 }
 }
 
 | 
observeArray方法中对inserted进行遍历,对每一项进行监听。为什么要遍历呢?因为inserted不一定是一个值。也有可能是多个如:[].splice(0,0,”1”, “2”, “3”);[].push(1,2,3)等。
我们来看 observer方法:
| 12
 3
 4
 5
 6
 7
 
 | function observer(value) {
 if (!value || typeof value !== "object") {
 return;
 }
 return new Observer(value);
 }
 
 | 
这里有很重要的一点:value不是一个对象的话, 我们是不做任何处理的。就比如:  observer(items[i]); // 注意这里是observer 不是 Observer这一句,这里的items[i]有可能就只是一个数据,而不是对象或者数组, 我们就是不处理的。不然跟对数组的所有下表监听的有啥区别。
目前实现对了数组方法的拦截。但是还有一个问题,就是我们在初始化的时候,data可能就是数组,因此要把这个数组也进行监听。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | function Observer(data) {if (Array.isArray(data)) {
 data.__proto__ = arrayMethodObj;
 observerArray(data);
 } else {
 Object.keys(data).forEach((key) => {
 defineObserver(data, key, data[key]);
 })
 }
 }
 
 | 
最后我们是实现了对数据的监听,不过这里还是有个问题没解决,也就是Vue2.x还没有解决的问题:并没有实现对数组的每一项进行监听:如下面这样的就不会被监听到
| 12
 3
 4
 5
 6
 7
 8
 
 | const vm = new MyVue({el: "#app",
 data: {
 name: "舒丽琦",
 list: ["1", "2", "3"]
 }
 })
 vm.list[0] = "我改变了"
 
 | 
这是因为我们数据劫持的时候没有对数组的下标进行监听。因为性能的代价太高了。除此之外,改变数组的长度也是无限监听的 vm.list.length = 9。
结果
最后我们演示一下结果
我们的html代码为:
| 12
 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
 
 | <html><header></header>
 <body>
 <div id="app"></div>
 </body>
 <script src="./js/observer.js"></script>
 <script src="./js/index.js"></script>
 <script src="./js/compile.js"></script>
 <script src="./js/watcher.js"></script>
 <script>
 const vm = new MyVue({
 el: "#app",
 data: {
 name: "舒丽琦",
 list: ["舒", "丽", "琦"]
 }
 })
 
 vm.list.push(["小小舒", "sha"]);
 
 
 vm.list[3].push("哈哈哈")
 
 
 vm.list.splice(3, 0,"哈哈哈", "怎么着");
 
 console.log(vm.list);
 </script>
 </html>
 
 | 
observer.js完整的代码:
| 12
 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
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 
 | 
 const oldArrayProperty = Array.prototype;
 
 
 const arrayMethodObj = Object.create(oldArrayProperty);
 
 
 const arrayMethods =  ["push", "shift", "unshift", "pop","reverse", "sort", "splice"];
 arrayMethods.forEach(method => {
 arrayMethodObj[method] = function(...arg) {
 
 const result = oldArrayProperty[method].apply(this, arg);
 
 
 let inserted;
 switch(method) {
 case "push":
 case "unshift":
 inserted = arg;
 break
 case "splice":
 
 inserted = arg.slice(2);
 break
 default:
 break;
 }
 if (inserted) {
 
 observerArray(inserted);
 }
 console.log(`数组有变化了,方法:${method}, 新增加的值为: ${inserted}`);
 
 
 return result;
 }
 })
 
 
 
 function Observer(data) {
 if (Array.isArray(data)) {
 data.__proto__ = arrayMethodObj;
 observerArray(data);
 } else {
 Object.keys(data).forEach((key) => {
 defineObserver(data, key, data[key]);
 })
 }
 }
 function observerArray(items) {
 for (let i = 0; i < items.length; i++) {
 observer(items[i]);
 }
 }
 
 function observer(value) {
 
 if (!value || typeof value !== "object") {
 return;
 }
 return new Observer(value);
 }
 
 function defineObserver(data, key, value) {
 
 observer(value);
 const dep = new Dep();
 Object.defineProperty(data, key, {
 get: function() {
 
 if (Dep.target) {
 dep.addSub(Dep.target)
 }
 return value;
 },
 set: function(newValue) {
 if (value !== newValue) {
 console.log("监听对象属性到变化了,新的值为:", newValue)
 value = newValue;
 
 dep.notify();
 }
 }
 })
 }
 
 
 
 
 function Dep() {
 this.subs = [];
 }
 Dep.prototype = {
 addSub: function(sub) {
 this.subs.push(sub);
 },
 notify: function() {
 this.subs.forEach((sub) => {
 sub.update();
 })
 }
 };
 Dep.target = null;
 
 | 
结果:
 
上面例子的代码: vue检测数组-重写数组常用的方法