深入解析 ES6 的继承
学习,就是不断推翻原有知识体系,跳出去建构更庞大的体系的一个过程。期间越是觉得迷惑不解,越是说明你掌握了新的东西。
今天看到一篇文章,博主遇到了如何继承 Date 对象的问题。博主很优秀,讲了好几个如何实现例子,但是文章看的我很迷糊。我觉得重点不是如何去实现,而是去明白产生问题的根源,所以有了这篇博客,从我认为可以理解的角度讲一讲。
之前写了一篇博客讲了如何用 es5 实现 Class 的继承,有兴趣可以看看 ES6 Class 的原生写法
一、问题产生背景
首先,我们要知道在 ES5 中,原生构造函数是无法继承的。
ECMAScript 的原生构造函数大致有下面这些:
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
然而 ES6 却允许使用 extends 继承原生构造函数定义子类。
二、问题的根源
ES5 与 ES6 在继承上是有区别,而又通过原生构造函数的某个特殊性质,导致他们显现不同的状态。
所以我们要先弄清楚原生构造函数的特殊性,再看继承的不同表现
(1)原生构造函数特殊性: this 无法绑定
原生构造函数无法强制绑定 this
例子:
var e = {};
Object.getOwnPropertyNames(e)
//输出:[]
Array.call(e)
Object.getOwnPropertyNames(e)
//输出:[]
(2)ES5 与 ES6 继承的区别
1. ES5 继承
由于原生构造函数 this 无法强制绑定,我们看看 ES5 继承产生的问题
例子:
function MyArray() {
Array.apply(this, arguments); //发生问题
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
});
var colors = new MyArray();
colors[0] = "red";
colors[1] = "blue";
//color: [0: "red", 1: "blue"]
colors.length = 0;
//color: [0: "red", 1: "blue"]
Array 构造函数有一个内部属性[[DefineOwnProperty]],用来定义新属性时,更新length属性,这个内部属性无法在子类获取,导致子类的length属性行为不正常。
所以原生构造函数的实例方法只能由该构造函数的实例调用
2.ES6 继承
我们再看看 ES6 的例子:
class MyArrayES6 extends Array {
constructor() {
super();
}
}
var colors = new MyArrayES6();
colors[0] = "red";
colors[1] = "blue";
//color: [0: "red", 1: "blue"]
colors.length = 0;
//color: []
那 ES6 的继承为什么有没问题呢?
原来,ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Array.apply)。
ES6 是先新建父类的实例对象 this(所以必须先调用super方法),然后再用子类的构造函数修饰 this,使得父类的所有行为都可以继承。
三、如何继承 Date 对象
好了,既然问题的根源我们知道了,那解决方法就出来了。
1.ES6 extends 继承
class MyDate extends Date{
constructor(){ super() }
}
注意:如果是用 ES6 写法然后 Babel 打包,转化后还是变成 ES5 的组合寄生式继承写法,会报错
2.模拟 ES6 继承
先创建父类的实例,使得该实例的 this 可以访问内部属性;然后修改该实例的 _proto_
指向子类的 prototype,形成继承的效果。
继承构造函数 Array 例子:
function MyArray(){
var _a = new Array()
Object.setPrototypeOf(_a,MyArray.prototype)
return _a
}
Object.setPrototypeOf(MyArray.prototype,Array.prototype)
var a = new MyArray();
a = ['red','blue'];
a.length = 1; // color:['red']
继承构造函数 Data 例子:
function MyDate(){
var _d = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();
Object.setPrototypeOf(_d,MyDate.prototype)
return _d
}
MyDate.prototype = Object.create(Date.prototype)
Object.defineProperty(MyDate.prototype,'constructor',{
enumerable:'false',
value:MyDate
})
var d = new MyDate()
d.getDate(); //可以调用实例方法
四、总结
1.JS 原型继承的经典写法是为了兼容 ES 3 不能修改对象 proto,要继承原生对象内置功能,应该按 ES 6 的思路写 ES 5 继承。
2.JS 并没有真正的类,都是生成特定对象的函数;这里的继承也不像其他语言上复制继承,而是靠原型链的关联特性达到继承效果。