简介
Class 可以通过 extends 关键字实现继承,比Es5通过修改原型链实现继承,更清晰和方便。
class Point {}
class ColorPoint extends Point {}
ColorPoint 通过extends 继承了 point 的所有方法和属性。两个类一样。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y)
this.color = color
}
toString() {
return this.color + ' ' + super.toString() // 调用父类的toString()
}
}
super 关键字 表示父类的构造函数,用来新建父类的 this 对象。
子类必须在contructor 方法中调用super 方法,否则新建实例时会报错。
子类的this 对象,必须先通过父类的构造函数完成塑造,得到父类同样的实例属性和方法,然后再对其加上自己的实例属性和方法。如果不调用super 方法,子类就不能得到this 对象。
class Point {}
class ColorPoint extends Point {
constructor() {}
}
let cp = new ColorPoint() // 报错
ColorPoint 继承了父类Point, 但它的构造函数没有调用 super 方法,导致新建实例时报错。
Es6 继承机制:
先将父类实例对象的属性和方法,加到this 上面(必须先调用super方法),然后再用子类的构造函数修改this.
如果子类没有定义constructor 方法,这个方法会被默认添加。
class ColorPoint extends Point {}
// 等同于
class ColorPoint exntends Point {
construcotr(...args) {
super(...args)
}
}
注意: 在子类的构造函数中,只有调用super 方法后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super 方法才能调用父类实例。
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color // 报错
super(x, y)
this.color = color // 正确
}
}
子类的constructor 方法this 关键字,必须要在super() 调用后使用。
let cp = new ColorPoint(25, 8, 'green')
cp instanceof ColorPoint // true
cp instanceof Point // true
cp 同时是ColorPoint和Point 两个类的实例。
父类的静态方法也会被子类继承
class A {
static hello() {
console.log('hello world')
}
}
class B extends A {}
B.hello() // 'hello world'
Object.getPrototypeOf()
Object.getPrototypeOf 方法可从子类获取父类
Object.getPrototypeOf(ColorPoint) === point // true
用这个方法判断: 一个类是否继承了另一个类。
super 关键字
可以当函数使用,也可以当对象使用。
super =》函数
super 方法,代表父类的构造函数。Es6 要求,子类的构造函数必须执行一次super函数。
class A {}
class B extends A {
construcor() {
super()
}
}
super 虽然代表了父类A 的构造函数,但返回的是子类B的实例,即super 内部的 this 值的是B的实例。
super() => A.prototype.constructor.call(this)
class A {
constructor() {
console.log(new.target.name)
}
}
class B extends A {
constructor() {
super()
}
}
new A() // A
new B() // B
new.target 指向当前正执行的函数。所以,supe() 执行时,指向的是子类B的构造函数,而不是父类A的构造函数。
作为函数时,super() 只能用在子类的构造函数中,用在其它地方会报错。
class A {}
class B extens A {
m() {
super() // 报错
}
}
super() 用在B类的m方法中,就会导致语句报错。
super 作为对象
super 作为对象时,
在普通方法中,指向父类的原型对象;
在静态方法中, 指向父类。
class A {
p() {
return 2
}
}
class B extens A {
constructor() {
super()
console.log(super.p()); // 2
}
}
let b = new B()
子类B 中普通方法的 super,指向父类A.prototype, 所以 super.p() 相当于A.prototype.p()
由于super 指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super 调用的。
calss A {
constructor() {
this.p = 2
}
}
class B extends A {
get m() {
return super.p;
}
}
let b = new B()
b.m // undefined
p 是父类A实例的属性, super.p 就引用不到它。
如果属性定义在父类的原型对象上,super 就可以取到。
calss A {}
A.prototype.x = 2
class B extends A {
get m() {
return super.x;
}
}
let b = new B()
b.m // 2
属性x 定义在A.prototype 上, 所以 super.x 可以取到它的值。
Es6规定,在子类的普通方法中通过super 调用父类的方法时,方法内部的this指向当前的子类实例。
class A {
constructor() {
this.x = 1
}
print() {
console.log(this.x)
}
}
class B extends A {
constructor() {
super()
this.x = 2
}
m() {
super.print()
}
}
let b = new B()
b.m() // 2
super.print()虽然调用A.prototype.print(), 但是A.portotype.print() 内部的this 指向子类的B实例,导致输出的时2,而不是1.实际上执行的是 super.print.call(this)
由于this 指向子类实例,所以如果 super() 对某个属性赋值,这时super 就是 this, 赋值的属性会变成子类实例属性。
clas A {
constructor() {
this.x = 1
}
}
class B extens A {
constructor() {
super()
this.x = 2
super.x = 3
console.log(super.x) // undefined
console.log(this.x) // 3
}
}
let b = new B()
supper.x = 3, 等同于 this.x = 3. 当读取super.x 时,读取的是 A.prototype.x, 因为 A 的原型上没有x 属性,返回undefined.
如果super 作为对象,用在静态方法中,这时 super 指向父类,而不是父类的原型对象。
class Parent {
static myMethod(msg) {
console.log('static', msg)
}
myMethod(msg) {
console.log('instance', msg)
}
}
class Child extends Parent {
static myMethod(msg) {
supe.myMethod(msg)
}
myMethod(msg) {
super.myMethod(msg)
}
}
Child.myMethod(1) // static 1
let child = new Child()
child.myMethod(2) // instance 2
super 在静态方法中指向父类,在普通方法中指向父类的原型对象。
在子类的静态方法中通过super调用父类的方法时,方法内部的this 指向当前子类,而不是子类的实例。
class A {
constructor() {
this.x = 1;
}
static print() {
console.log(this.x)
}
}
class B extends A {
constructor() {
super()
this.x = 2
}
static m() {
super.print()
}
}
B.x = 3
B.m() // 3
静态方法 B.m 中, super.print 指向父类的静态方法。这个方法里 this 指向的是 B,而不是B的实例。
注意: 使用super 时, 必须显示指定是作为函数、还是作为对象使用,否则会报错。
class A {}
class B extends A {
constructor() {
super()
console.log(super); // 报错
console.log(super.valueof() instanceof B); // true
}
}
let b = new B()
console.log(super)中的super,无法看出是作为函数使用,还是作为对象使用,会报错。
super.valueOf() 表明super是一个对象,因此不会报错。由于 super使得this指向B的实例,所以super.valueOf() 返回的是一个B的实例。
由于对象总是继承其它对象的,所以可以在任意一个对象中,使用super 关键字。
let obj = {
toString() {
return 'MyObject:' + super.toString()
}
}
obj.toString(); // MyObject: [Object Object]
类的 prototype 属性和 proto 属性
Class 同时有 prototype 属性和 proto属性,因此同时存在两条继承链。
子类的proto 属性,表示构造函数的继承,总是指向父类。
子类prototype属性的proto 属性,表示方法的继承,总是指向父类的prototype 属性。
class A {}
class B extends A {}
B.__proto__ === A; // true
B.prototoype.__proto__ === A.prototype
类的继承是按照下面的模式实现的。
class A {}
class B {}
// B 的实例继承 A的实例
Object.setPrototypeOf(B.prototype, A.prototype)
// B 继承 A 的静态属性
Object.setPrototype(B, A)
Object.setPrototypeOf 方法的实现
Object.setPrototypeOf = function(obj, proto) {
obj.__proto__ = proto
return obj
}