Array 继承
在vue
中监听数组变化的做法是重新包装数组的push、pop、shift
等方法,每次调用这些方法时都会去通知相应的观察者:
1 | const arrayProto = Array.prototype; |
最后在每次监听数组数据时,都会把arrayMethods
当做数组数据的原型:
1 | array.__proto__ = arrayMethods; |
问题:
- 为什么不直接修改原生
Array
上的这些方法,如Array.prototype.push = function(){ /* ... */ }
- 上面的实现跟继承很相似,即子类的大部分方法跟父类一样,但也可以重写一些方法。可不可以实现一个自定义的
Array
子类,然后array.__proto__ = MyArray;
?
第一个问题很好回答,因为如果直接修改原生Array
,会影响到所有使用数组的代码,肯定不希望在一些与vue
无关的地方调用push
结果还触发了数据监听。
第二个问题可以先做一个尝试:
1 | function MyArray() { |
上面就是一个常见的寄生组合继承,打印一下myArr
:
可以看到很奇怪的是myArr
的值不是我们传入的1,2,3,4
,这一点与原生Array
的行为有差异:
1 | var arr = new Array(1, 2, 3); // [1,2,3] |
原因是因为 Array 构造函数不会对传进去的 this 做任何处理,其他原生类型的构造函数如 Error、Object 等也是这样:
1 | var o = {}; |
所以上面的myArr
在初始化时就是一个空对象,不会被Array
附加属性,调用push
时也只是在操作这个空对象。从始至终,myArr
就不是一个真正的数组。
数组有两个关键的的属性:
- 响应式的
length
属性,会自动根据元素增加而增加,并且如果减少 length,会自动删除多余的元素 [[class]]
内部属性(见baseGetTag
的描述),他是Array.isArray
和Object.prototype.toString.call
的依据,我们无法改变它。
如果真的要在自定义的构造函数里获得一个原生的数组对象,只能直接在构造函数里初始化一个数组变量,然后设置这个变量的__proto__
属性:
1 | function MyArray2() { |
在 ES6 中情况有了一些改变,参考es6 入门:
ES5 是先新建子类的实例对象 this,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数。
ES6 允许继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象 this,然后再用子类的构造函数修饰 this,使得父类的所有行为都可以继承。
所以我们可以用 ES6 的extends
来继承原生的Array
:
1 | class MyArray3 extends Array { |