接着上一篇,本篇内容将介绍如何监听数组
一、分析
相比较对象,数组就有点复杂了,为什么要这样说?对于对象我们多是通过key操作的,但是数组的操作就多了:
- 通过length对数组的修改;
- 通过Index访问和修改;
- 通过Array.prototype上的方法修改。
1、length的监听
const b = [1,2,3];
console.log(Object.getOwnPropertyDescriptor(b,'length'));复制代码
数组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);复制代码
数组的splice方法并不会产生尴尬的undefined
这也就是为什么在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__属性。这里我们可以这样改写:
function OBArray() {
const arr = Array.apply(null,arguments);
arr.__proto__ = OBArray.prototype;
arr.constructor = OBArray;
return arr;
}复制代码
到这里基本就是实现数组监听的需求了,虽然源码也是这样的思想,不过人家代码写的优雅啊Vue数组监听的代码部分,很值得学习呀。
人生不能留下遗憾,编代码也一样。所以我们来看ES6给我们带来的便利:
1、class-extends
文中清楚的解释到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入门,代码如下:
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这个对象的特点,哎,基础并不是我们想象的那么基础呀!