类 | 类继承

继承基础

使用extends关键字就可以继承任何拥有[[Construct]]和原型的对象

  • 不仅可以继承一个类,也可以继承普通的构造函数(保持向后兼容)【任何可以解析为一个类或者一个构造函数的表达式都是有效的】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {}

// 继承类
class Women extends Person {}

let w = new Women(); // 新建实例
console.log(w instanceof Women); // true
console.log(w instanceof Person); // true

function Person() {}

// 继承普通的构造函数
class Men extends Person {}

let m = new Men();
console.log(m instanceof Men); // true
console.log(m instanceof Person); // true
  • 派生类可以通过原型链访问到类上的方法和原型上定义的方法,this的值会反映调用相应方法的类或者实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person {
sayHelloPorototype(name) {
console.log(`${name} say hello, ${this}`);
}

static sayHelloClass(name) {
console.log(`${name} say hello, ${this}`);
}
}

class Women extends Person{}

let p1 = new Person();
let w1 = new Women();

p1.sayHelloPorototype('Katrina'); // Katrina say hello, Person{}
w1.sayHelloPorototype('Jack'); // Jack say hello, Women{}

Person.sayHelloClass('Katrina1'); // Katrina1 say hello, class Person {}
Women.sayHelloClass('Jack1'); // Jack1 say hello, class Women extends Person{}
  • extends也可以在类表达式中使用
1
2
3
4
5
6
7
8
9
10
11
class Person {
sayHelloPorototype(name) {
console.log(`${name} say hello, ${this}`);
}

static sayHelloClass(name) {
console.log(`${name} say hello, ${this}`);
}
}

let Women = class extends Person {}

构造函数、HomeObject和super()

派生类可以通过super关键字引用它们的原型

  • 构造函数:在类构造函数中使用super可以调用父类构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Vehicle {
constructor() {
this.hasEngie = true;
}
}

class Bus extends Vehicle {
constructor() {
super(); // 相当于super.constructor()

// 注意:千万不要在调用super()之前引用this,否则会抛出ReferenceError
console.log(this instanceof Vehicle); // true
console.log(this); // Bus {hasEngie: true}
}
}

new Bus();
  • 静态方法:在静态方法中可以通过super 调用继承的类上定义的静态方法
1
2
3
4
5
6
7
8
9
10
11
12
13
class Vehicle {
static identify() {
console.log('vehicle');
}
}

class Bus extends Vehicle {
static identify() {
super.identify();
}
}

Bus.identify(); // 'vehicle'

使用super时要注意的几个问题

  • super只能在派生类的构造函数和静态方法中使用
1
2
3
4
5
6
class Vehicle {
constructor() {
super();
// SyntaxError: 'super' keyword unexpected
}
}
  • 不能单独调用super关键词,要么用它调用构造函数,要么用它引用静态方法
1
2
3
4
5
6
7
class Vehicle {}
class Bus extends Vehicle {
constructor() {
console.log(super);
// SyntaxError: 'super' keyword unexpected here
}
}
  • 调用super()会调用父类构造函数,并将返回的实例赋值给this(也就是this指向实例)
1
2
3
4
5
6
7
8
9
10
11
class Vehicle {}

class Bus extends Vehicle {
constructor() {
super(); // 调用父类构造函数
console.log(this instanceof Vehicle); // true
console.log(this); // Bus{}
}
}

new Bus();
  • super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入
1
2
3
4
5
6
7
8
9
10
11
12
13
class Vehicle {
constructor(id) {
this.id = id;
}
}

class Bus extends Vehicle {
constructor(id) {
super(id);
}
}

console.log(new Bus(1234).id); // 1234
  • 如果没有定义派生类构造函数,在实例化派生类时会调用super(),而且会传入所有传给派生类的参数
1
2
3
4
5
6
7
8
9
class Vehicle {
constructor(id) {
this.id = id;
}
}

class Bus extends Vehicle {}

console.log(new Bus(1234).id); // 1234
  • 在类构造函数中,不能在调用super()之前引用this
1
2
3
4
5
6
7
8
9
10
class Vehicle {}

class Bus extends Vehicle {
constructor() {
console.log(this);
}
}
new Bus();
// ReferenceError: Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor
  • 如果在派生类中显式定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回一个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Vehicle {}

class Car extends Vehicle {}

class Bus extends Vehicle {
constructor() {
super();
}
}

class Van extends Vehicle {
constructor() {
return {};
}
}

class Jeep extends Vehicle {
constructor() {
name: 'Katrina'
}
}

console.log(new Car()); // Car {}
console.log(new Bus()); // Bus {}
console.log(new Van()); // {}
console.log(new Jeep()); // Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

抽象基类

抽象基类:可以供其他类继承,但是本身不会实例化

可以通过判断new.target来阻止实例化

补课:new.target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 抽象基类
class Vehicle {
constructor() {
console.log(new.target);
if (new.target === Vehicle) { // 就说明抽象基类被用new实例化了
throw new Error('Vehicle cannot be directly instantiated');
}
}
}


// 派生类
class Bus extends Vehicle{} // 抽象基类是可以被继承的

new Bus(); // class Bus {}
new Vehicle(); // class Vehicle {}

// Uncaught Error: Vehicle cannot be directly instantiated
  • 通过在抽象基类构造函数中进行检查,可以要求派生类必须定义某个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 抽象基类
class Vehicle {
constructor() {
console.log(new.target);
if (new.target === Vehicle) { // 就说明抽象基类被用new实例化了
throw new Error('Vehicle cannot be directly instantiated');
}

if (!this.foo) {
throw new Error('Inheriting class must define foo()');
};

console.log('success');
}
}

// 派生类
class Bus extends Vehicle {
foo() {}
}


class Van extends Vehicle {}

new Bus(); // success
new Van(); // Inheriting class must define foo()

基础内置类型

类继承内置引用类型方便扩展内置类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SuperArray extends Array {   // 注意:之前提到过extends不仅可以继承一个类,也可以继承普通的构造函数
shuffle() {
// 洗牌算法
for (let i = this.length - 1; i >= 0; i--) {
const j = Math.floor(Math.random() * (i+1));
[this[i], this[j]] = [this[j], this[i]];
}
}
}

let a = new SuperArray(1,2,3,4,5);

console.log(a instanceof SuperArray); // true
console.log(a instanceof Array); // true

console.log(a); // [1, 2, 3, 4, 5]
a.shuffle();
console.log(a); // [3, 1, 2, 5, 4]
  • 有些内置类型的方法会返回新实例,默认情况下,返回实例的类型与原始实例的类型是一致的
1
2
3
4
5
6
7
8
9
class SuperArray extends Array {}

let a1 = new SuperArray(1, 2, 3, 4, 5);
let a2 = a1.filter(x => !!(x%2))

console.log(a1); // [1, 2, 3, 4, 5]
console.log(a2); // [1, 3, 5]
console.log(a1 instanceof SuperArray); // true
console.log(a2 instanceof SuperArray); // true
  • 如果想覆盖这个默认行为,则可以覆盖Symbol.species访问器,这个访问器决定在创建返回的实例时使用的类
1
2
3
4
5
6
7
8
9
10
11
12
13
class SuperArray extends Array {
static get [Symbol.species]() {
return Array;
}
}

let a1 = new SuperArray(1, 2, 3, 4, 5);
let a2 = a1.filter(x => !!(x%2))

console.log(a1); // [1, 2, 3, 4, 5]
console.log(a2); // [1, 3, 5]
console.log(a1 instanceof SuperArray); // true
console.log(a2 instanceof SuperArray); // false

类混入

类混入:把不同类的行为集中到一个类

混入模式可以通过在一个表达式中连缀多个混入元素来实现,这个表达式最终会解析为一个可以被继承的类

假设现在有一个需求:如果Person类需要组合ABC,则需要某种机制实现B 继承AC 继承B,而Person再继承C,从而把AB、`C 组合到这个超类中

实现策略:定义一组“可嵌套”的函数,每个函数分别接收一个超类作为参数,而将混入类定义为这个参数的子类,并返回这个类,这些组合函数可以连缀调用,最终组合成超类表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Vehicle{}

let FooMixin = (Superclass) => class extends Superclass {
foo() {
console.log('foo');
}
};

let BarMixin = (Superclass) => class extends Superclass {
bar() {
console.log('bar');
}
};

let BazMixin = (Superclass) => class extends Superclass {
baz() {
console.log('baz');
}
};

class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {}

let b = new Bus();
b.foo(); // foo
b.bar(); // bar
b.baz(); // baz

通过写一个辅助函数,可以把嵌套展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Vehicle{}

let FooMixin = (Superclass) => class extends Superclass {
foo() {
console.log('foo');
}
};

let BarMixin = (Superclass) => class extends Superclass {
bar() {
console.log('bar');
}
};

let BazMixin = (Superclass) => class extends Superclass {
baz() {
console.log('baz');
}
};


function mix(BassClass, ...Minins) {
return Minins.reduce((prev, curr) => curr(prev), BassClass)
}

class Bus extends mix(Vehicle, FooMixin, BarMixin, BazMixin);

let b = new Bus();
b.foo(); // foo
b.bar(); // bar
b.baz(); // baz