Class 可以通过 extends
关键字实现继承。
class Point{
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return `( ${this.x}, ${this.y})`
}
}
class ColorPoint extends Point{
constructor(x, y, color){
super(x, y);
this.color = color;
}
toString(){
return `${this.color} ${super.toString()}`;
}
}
var cp = new ColorPoint(1, 2, 'red');
console.log(cp.toString()); => red ( 1, 2)
注意
(1)如果在子类中显式定义了 constructor
则子类必须在 constructor
方法中调用 super
方法,否则会报错。因为子类没有自己的 this
对象,而是继承父类的 this
对象,然后进行加工,如果不调用 super
方法,子类就得不到 this
对象。并且 this
的使用是在 super
方法调用之后。否则会报错。
class Poi{}
class CPoi{
constructor(){}
}
var cpo = new CPoi(); => 报错 this is not defined
--------------------------------------------------
class Poi{}
class CPoi{
constructor(c){
this.c = c;
super();
}
}
var cpo = new CPoi(); => 报错 this is not defined
(2)子类的实例对象既是 子类的实例 又是 父类的实例。
cp instanceof ColorPoint => true;
cp instanceof Point => true;
(3)Object.getPrototypeOf()
方法可以用来从子类上获取父类。可以使用该方法判断,一个类是否继承了另一个类。
Object.getPrototypeOf(ColorPoint) === Point => true
super 这个关键字,既可以当作函数使用,又可以当作对象使用。在这两种情况下,它的用法完全不同。
(1)super 作为函数调用
super 作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次 super()
。
注意:
1)super 虽然代表了父类的构造函数,但是返回的是子类的实例,即 super 内的 this 指的是子类,因此 super()
相当于 Point.prototype.constructor.call(this)
。参见new.target属性。
2)作为函数时,super()
只能用在子类的构造函数中,用在其他地方将报错。
(2)super 作为对象
super 作为对象时,在普通方法中,指向父类的原型对象,在静态方法中,指向父类。
例如 ColorPoint
的 toString
方法中的 super.toString()
就是将 super 当作一个对象使用,这时,super 在普通方法中,指向 Point.prototype
,所以 super.toString()
相当于 Point.prototype.toString()
。
注意:
1)由于 super 在普通方法中指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过 super 调用的。
class A { constructor(){this.x = 2;}}
class B extends A { getX() { return super.x;} }
let b = new B();
b.getX() => undefined
如果属性定义在父类的原型对象上,super 就可以取到。
class A{}
A.prototypy.x = 2;// 此时 B 中可通过 super.x 的到 x
2)ES6 规定,通过 super 调用父类的方法时, super 会绑定子类的 this。
3)由于绑定子类的 this ,所以如果通过 super 对某个属性赋值,这时 super 就是 this,赋值的属性会变成子类实例的属性。
class A {
constructor(){
this.x = 2;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
上述代码中,赋值时 super 就是 this ,即 super.x = 3 => this.x = 3
,而当读取时 super 代表父类的原型对象,即 super.x => A.prototype.x
返回 undefined
。
4)使用 super 时,必须显式指定是作为函数,还是作为对象,否则会报错。
如果 super 作为对象,用在静态方法中,这时 super 将指向父类,而不是父类的原型对象。
每个对象都有 __proto__
属性,指向对应的构造函数的 prototype 属性。Class 作为构造函数的语法糖,同时有 prototype 属性和 __proto__
属性,因此同时存在两条继承链。
(i)子类的 __proto__
属性,表示构造函数的继承,总是指向父类。
(ii)子类 prototype 属性的 __proto__
属性,表示方法的继承,总是指向父类的 prototype 属性。
B.__proto__ === A // true
B.prototype.__proto__ = A.prototype // true
子类实例的 __proto__
属性的 __proto__
属性,指向父类实例的 __proto__
属性。也就是说,子类的原型的原型,是父类的原型。
b.__proto__.__proto__ === a.__proto__ // true
(1)extends 的继承目标
extends
关键字后面可以跟多种类型的值。
(1)函数
由于函数都有 prototype 属性(除了 Function.prototype 函数),因此,可以跟任何函数。
(2)Object
子类继承 Object 类,这种情况下,子类其实就是构造函数 Object 的复制, 子类的实例就是 Object 的实例。
class A extends Object {}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
(3)不存在任何继承
这种情况下,A 作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承 Function.prototype
。但是, A 调用后返回一个空对象(即 Object 实例),所以 A.prototype.__proto__
指向构造函数(Object)的 prototype 属性。
class A {}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true
(4)子类继承 null
这种情况与第三种情况非常像。 A 也是一个普通函数,所以直接继承 Function.prototype
。但是,A 调用后返回的一个 undefined 。
class A extends null {}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true
=> 相当于
class A extends null {
constructor(){ return Object.create(null);}
}
原生构造函数是指语言内置的构造函数,通常用来生成数据结构。 ECMAScript 的原生构造函数大致有以下:
以前,这些原生构造函数无法继承,参见 ES5 的继承。ES6 允许使用 extends
继承原生构造函数定义子类,因为 ES6 是先新建父类的实例对象 this ,然后在用子类的构造函数修饰 this ,使得父类的所有行为都可以继承。
注意:继承 Object 的子类,有一个行为差异。
class NewObj extends Object{
constructor(){
super( ...arguments);
}
}
var o = new NewObject({attr: true});
o.attr === true // false
上面代码中, NewObj
继承了 Object
,但是无法通过 super
方法向父类 Object
传参。这是因为 ES6 改变了 Object
构造函数的行为,一旦发生 Object
方法不是通过 new Object()
这种形式调用,ES6 规定 Object
构造函数会忽略参数。
Mixin 模式是指,将多个类的接口 “混入”(mix in)另一个类。 它在 ES6 的实现如下:
function mix(...mixins){
class Mix{}
for(let mixin of mixins){
copyPrototypes(Mix, mixin);
copyPrototypes(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyPrototypes(target, source){
for(let key of Reflect.ownKeys(source)){
if( key !== "constructor"
&& key !== "prototype"
&& key !== "name" ){
let desc = Object.getOwnPrototyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
上面的 mix 函数,可以将多个对象合成一个类,使用的时候只要继承这个类即可。
class DistributedEdit extends mix(Loggable, Serializable) {}
阮一峰:ECMAScript 6入门