接著上一篇,本篇內容將介紹如何監聽陣列
一、分析
相比較物件,陣列就有點複雜了,為什麼要這樣說?對於物件我們多是通過key操作的,但是陣列的操作就多了:
- 通過length對陣列的修改;
- 通過Index訪問和修改;
- 通過Array.prototype上的方法修改。
1、length的監聽
const b = [1,2,3];
console.log(Object.getOwnPropertyDescriptor(b,'length'));複製程式碼
一看到configuarable的值為false,我們就很絕望了,通過get,set方法來監聽它的路子是走不通了。(方法後面會講。)
2、index的監聽
對於陣列的下標是可以採用defineProperty定義set和get方法,但是我們要注意一個問題:
const arr = [];
arr[3] = 1;複製程式碼
對於上面這段程式碼,我們雖然只設定了index=3的資料,但是下標0~2會被自動填充為undefined,它的length也變成了4,所以這裡存在的問題很隱晦。
當我們將一個陣列的下標通過defineProperty完成監聽的時候是沒什麼問題的,但是這時我們執行上面那樣的操作時,和之前的物件一樣要採用set方法新增新的下標,但是前面未定義的下標就失控了。但是下面這種方法就可以規避這種風險:
const arr = [];
arr.splice(3,1,10);複製程式碼
這也就是為什麼在Vue中要通過下標訪問修改陣列時要採用set方法,而Vue的set方法的內部正是採用了splice解決這種問題。
3、陣列原型方法的監聽
首先我們可以通過繼承Array物件,重寫原型方法實現我們的需求,那麼我們先來實現一個繼承重寫的例子:
function Shape() {
this.x = 0;
this.y = 0;
}
Shape.prototype.move = function (x,y) {
this.x += x;
this.y += y;
console.log('shape move');
}
function Rectangle() {
Shape.call(this);
}
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
Rectangle.prototype.move = function (x,y) {
console.log('監聽父類的move方法');
Shape.prototype.move.call(this,x,y);
}
const rect = new Rectangle();
rect.move(20,10);複製程式碼
這裡我們通過子類Rectangle重寫父類Shape的move方法,達到監聽的效果。是不是我們將這裡的Shape換成Array不就行了嗎?上程式碼:這裡暫且只監聽push、pop和splice,其他的基本一樣。
function OBArray() {
Array.apply(this, arguments);
}
OBArray.prototype = Object.create(Array.prototype);
OBArray.prototype.constructor = OBArray;
OBArray.prototype.push = function () {
const len = Array.prototype.push.apply(this,arguments);
console.log('push: ' + arguments[0] + ' 新的陣列長度為:' + len);
return len;
}
OBArray.prototype.pop = function () {
const temp = Array.prototype.pop.call(this);
console.log('pop: ' + temp);
return temp;
}
OBArray.prototype.splice = function () {
console.log('採用了splice方法:');
return Array.prototype.splice.apply(this,arguments);
}
const arr = new OBArray(1,2,3,4,5);複製程式碼
這時問題就來了,你會發現只能的arr是一個空物件。但是Array原型鏈上的方法是繼承了。而這裡主要的原因就在於:
Array.apply(this, arguments);複製程式碼
ES6之前,JS內建物件的建構函式多不會去處理這裡的this。所以我們這裡雖然執行了Array的建構函式,但是並沒有繫結到我們此刻的this上。這裡Vue就採用了一個相當好的方法,__proto__屬性。關於__proto__和prototype的區別,所以這裡我們可以這樣改寫:
function OBArray() {
const arr = Array.apply(null,arguments);
arr.__proto__ = OBArray.prototype;
arr.constructor = OBArray;
return arr;
}複製程式碼
到這裡基本就是實現陣列監聽的需求了,雖然原始碼也是這樣的思想,不過人家程式碼寫的優雅啊Vue陣列監聽的程式碼部分,很值得學習呀。
人生不能留下遺憾,編程式碼也一樣。所以我們來看ES6給我們帶來的便利:
1、class-extends
這裡我們可以拜讀一下阮老師的Class的繼承。文中清楚的解釋到ES5和ES6繼承的差別,下面看一下實現的程式碼:
class OBArray extends Array {
constructor (...args) {
super(...args);
}
push (...args) {
const len = super.push(...args);
console.log('push: ' + args + ' 新的陣列長度為:' + len);
return len;
}
}
const a = new OBArray(...[1,2]);
a.push(20); // "push: 20 新的陣列長度為:3"複製程式碼
是不是非常的方便。
2、Proxy
對於Proxy入門,還是推薦阮老師的教程吧Proxy,程式碼如下:
let a = new Proxy([], {
get: function (target, key, receiver) {
console.log("讀取陣列");
return Reflect.get(target, key, receiver);
},
set: function (target, key, receiver) {
console.log('設定陣列');
return Reflect.set(target, key, receiver);
}
})
a.splice(3,1,10);複製程式碼
這裡控制檯輸出很有趣,有興趣的可以研究研究。也正是通過Proxy我們可以實現對length的監聽,不過相容性是硬傷啊。。。。
二、總結
到這裡基本上陣列的監聽的學習就完成啦。無論你用古老的"__proto__",還是用es6的extends和Proxy實現,我們最主要的還是要理解Array這個物件的特點,哎,基礎並不是我們想象的那麼基礎呀!
參考資料
知乎proto和prototype
阮老師的Class-extends
阮老師的Proxy
Vue core array.js原始碼您的關注是我學習的最大動力^_^