ECMAScript 6 - Class的继承
基本用法
Class之间可以通过extends
关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多
class Test extends Component {}
上面代码定义了一个Test
类,该类通过extends
关键字,继承了Component
类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Component
类。下面,我们在Test
内部加上代码。
class Test extends Component {
constructor(props) {
super(props); // 调用父类constructor(props)
}
toTest() {
super.toTest(); // 调用父类toTest()
}
}
上面代码中,constructor
方法和toTest
方法之中,都出现了super
关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类没有自己的this
对象,而是继承父类的this
对象,然后对其进行加工。如果不调用super
方法,子类就得不到this
对象
class Component {}
class Test extends Component {
cosntructor() {
}
}
let a = new Test();
上面代码中,Test
继承了父类Component
,但是它的构造函数没有调用super方法,导致新建实例时报错。
ES5的继承,实质是先创造子类的实例对象this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6的继承机制完全不同,实质是先创造父类的实例对象this
(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
如果子类没有定义constructor
方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor
方法。
constructor(...args) {
super(...args);
}
另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
class Component {
constructor(props) {
this.props = props;
}
}
class Test extends Component {
cosntructor(props, a) {
this.a = a; // ReferenceError
super(props);
this.a = a; // 正确
}
}
上面代码中,子类的constructor
方法没有调用super
之前,就使用this
关键字,结果报错,而放在super
方法之后就是正确的。
类的prototype属性和proto属性
大多数浏览器的ES5实现之中,每一个对象都有__proto__
属性,指向对应的构造函数的prototype
属性。Class
作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,因此同时存在两条继承链。
- 子类的
__proto__
属性,表示构造函数的继承,总是指向父类 - 子类
prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性
class A { }
class B extends A { }
B.__proto__ === A; // true
B.prototype.__proto__ === A.prototype; // true
上面代码中,子类B
的__proto__
属性指向父类A
,子类B
的prototype
属性的__proto__
属性指向父类A
的prototype
属性。
Extends的继承目标
extends
关键字后面可以跟多种类型的值
class B extends A {}
上面代码的A
,只要是一个有prototype
属性的函数,就能被B
继承。由于函数都有prototype
属性(除了Function.prototype
函数),因此A
可以是任意函数。
下面,讨论三种特殊情况。
第一种特殊情况,子类继承Object
类。
class A extends Object {
}
A.__proto__ === Object; // true
A.prototype.__proto__ === Object.prototype; // true
这种情况下,A
其实就是构造函数Object
的复制,A
的实例就是Object
的实例。
第二种特殊情况,不存在任何继承。
class A {}
A.__proto__ === Function.prototype; // true
A.prototype.__proto__ === Object.prototype; // true
这种情况下,A
作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Funciton.prototype
。但是,A
调用后返回一个空对象(即Object
实例),所以A.prototype.__proto__
指向构造函数(Object
)的prototype
属性。
第三种特殊情况,子类继承null
class A extends null {
}
A.__proto__ === Function.prototype; // true
A.prototype.__proto__ === undefined; // true
这种情况与第二种情况非常像。A
也是一个普通函数,所以直接继承Funciton.prototype
。但是,A
调用后返回的对象不继承任何方法,所以它的__proto__
指向Function.prototype
,即实质上执行了下面的代码。
class C extends null {
constructor() {
return Object.create(null);
}
}
Object.getPrototypeOf()
Object.getPrototypeOf
方法可以用来从子类上获取父类。
Object.getPrototypeOf(A) === B;
因此,可以使用这个方法判断,一个类是否继承了另一个类。
super关键字
super
这个关键字,有两种用法,含义不同。
- 作为函数调用时(即
super(...args)
),super
代表父类的构造函数 - 作为对象调用时(即
super.prop
或super.method()
),super
代表父类。注意,此时super
即可以引用父类实例的属性和方法,也可以引用父类的静态方法
class B extends A {
get m() {
return this._p * super._p;
}
set m() {
throw new Error('该属性只读');
}
}
上面代码中,子类通过super
关键字,调用父类实例的_p
属性。
由于,对象总是继承其他对象的,所以可以在任意一个对象中,使用super
关键字
var obj = {
toString() {
return "MyObject: " + super.toString();
}
};
obj.toString(); // MyObject: [object Object]
生命不息,折腾不止...
I'm not a real coder, but i love it so much!