继承和派生类
继承
在类中,我们只需要使用 extends
关键字指定继承的函数,通过 super()
方法范围基类的构造函数。
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
}
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
}
派生类
继承自其他类的类叫做 派生类
,如果在派生类中不指定构造函数,则在创建类实例的时候会自动调用 super()
并传入所有参数,但是最好主动调用 super()
方法。
派生类也可以继承父类的静态方法,这使用 es5
中的继承是做不到的。
super()
使用 super()
的注意点:
super()
只能使用在派生类的构造函数中,否则会报错。- 在构造函数中访问
this
时,一定要先调用super()
,否则会报错。 - 如果在派生类中指定了构造函数,就必须调用
super()
,否则会报错。如果不想调用super()
又不想报错,唯一方法是让构造函数返回一个 对象。
extends
只要表达式可以被解析成一个函数(最后的结果是一个函数),且具有 [[construct]]
属性和原型(箭头函数、生成器不可以),就可以使用 extends
进行派生。
mixin 方法实现多继承
function A() {...}
function B() {...}
function mixin(...minxins) {
var base = function() {}
Object.assign(base.prototype, ...minxins)
return base
}
class SubClass extends mixin(A, B) {{...}}
内建对象的继承
在ES6
之前,无法通过继承的方式创建属于自己的特殊数组的,也就是说可以使用原型链来继承数组的一些方法,但是还是有一些不同的:
- 子类生成的实例不是数组
[]
,而是一个对象{}
- 某些返回一个数组的方法(例如
slice
等),返回的值还是一个Array
的实例,而不是子类的实例。 - 没能从数组中继承其内部属性。有一个内部属性用于控制数值型属性和
length
属性同时更新,所以会导致通过下标添加元素时,length
值不变
function MyArray() {
Array.apply(this, arguments);
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
});
// 1
var arr = new Array(); // []
var colors = new MyArray(); // MyArray {}
// 2
colors.slice(0,1) // []
// 3
colors[0] = "red"; // MyArray {0: 'red'},此时colors.length仍为0
colors.push('blue'); // MyArray ['blue'],此时colors.length为1
但是使用 class
的 extends
关键字实现继承,是可以避免这些不同的。
继承内部属性
可以将内部属性一起继承的。
class MyArray extends Array {}
var colors = new MyArray(); // MyArray []
Symbol.species
原本在内建对象中返回实例自身的方法,将自动返回派生类的实例。
class MyArray extends Array {}
var colors = new MyArray(); // MyArray []
colors.slice(0,1) // MyArray []
在浏览器引擎背后是通过 Symbol.species
来实现的。Symbol.species
属性是一个静态的访问器属性,只有get
没有set
,会返回一个构造器。
很多内建对象中都定义了 Symbol.species
属性,其中的默认实现都是返回 this。
class MyClass {
static get[Symbol.species]() {
return this;
}
constructor(value) {
this.value = value;
}
}
派生类也会继承静态属性,所以派生类中也会有 Symbol.species
属性,同样返回 this
,这时的 this
指向的就是派生类了。当然我们也可以强制在 派生类中覆写 Symbol.species
属性,返回父类的构造器。
class MyClass {
static get[Symbol.species]() {
return this;
}
constructor(value) {
this.value = value;
}
clone() {
return new this.constructor[Symbol.species](this.value);
}
}
class SubClassl extends MyClass {
say(){...}
}
class SubClass2 extends MyClass {
static get[Symbol.species]() {
return MyClass;
}
say(){... }
}
let instancel = new MyDerivedClassl("foo"),
clonel = instancel.clone(), // SubClass1 {value: "foo"}
instance2 = new MyDerivedClass2("bar"),
clone2 = instance2.clone(); // MyClass {value: "bar"}
new.target
在类的构造函数中,也可以通过 new.target
确定类是如何被调用的,通常 new.target
等于类的构造函数。但是在 new
一个子类的时候,new.target
是子类的构造函数。