Vue的數組操作的實現代碼大致如下:
const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']; const arrayAugmentations = []; aryMethods.forEach((method)=> { // 這里是原生Array的原型方法 let original = Array.prototype[method]; // 將push, pop等封裝好的方法定義在對象arrayAugmentations的屬性上 // 注意:是屬性而非原型屬性 arrayAugmentations[method] = function () { console.log('我被改變啦!'); // 調用對應的原生方法並返回結果 return original.apply(this, arguments); }; }); let list = ['a', 'b', 'c']; // 將我們要監聽的數組的原型指針指向上面定義的空數組對象 // 別忘了這個空數組的屬性上定義了我們封裝好的push等方法 list.__proto__ = arrayAugmentations; list.push('d'); // 我被改變啦! 4 // 這里的list2沒有被重新定義原型指針,所以就正常輸出 let list2 = ['a', 'b', 'c']; list2.push('d'); // 4
在Vue的官方文檔中,有着如下的提示:
- 當你利用索引直接設置一個項時,例如:
vm.items[indexOfItem] = newValue
- 當你修改數組的長度時,例如:
vm.items.length = newLength
這個是Js語法的限制,什么限制呢?
先來看一下,在這篇文章中寫到,為什么不利用如下的代碼來實現:
function FakeArray() { return Array.call(this,arguments); } FakeArray.prototype = []; FakeArray.prototype.constructor = FakeArray; FakeArray.prototype.push = function () { console.log('我被改變啦'); return Array.prototype.push.call(this,arguments); }; let list = ['a','b','c']; let fakeList = new FakeArray(list);
然而,作者在測試代碼的時候,發現fakeList實際上是一個數組,而且它的push是內置的push方法,並不是繼承FakeArray的方法。
在作者文章的評論中,有個網友評論說,這是因為Array.call並不會引用this,不止Array,String,Number,Regexp,Object等等JS的內置類都不行。
所以實際上代碼是這樣的:
function FakeArray() { return Array(arguments); }
這也就是ES5以下無法完美繼承數組的問題,回過頭看一下Vue中的實現,Vue的作者用的是__proto__屬性,該屬性指向構造對象的原型。
也就是說,上面的例子我們可以這樣改寫:
function FakeArray() { var x = Array.call(null,arguments); x.__proto__ = FakeArray.prototype return x; } FakeArray.prototype = []; FakeArray.prototype.constructor = FakeArray; FakeArray.prototype.push = function () { console.log('我被改變啦'); return Array.prototype.push.call(this,arguments); }; let list = ['a','b','c']; let fakeList = new FakeArray(list);
但是呢,這樣寫,也就意味着我們不能檢測到length,和fakeList[x] = 1;這樣的操作,也就有了Vue文檔中的提示了。