十三、ES6系列之Class的继承

简介

在 ES5 中,实现继承的方式有多种,详细可以参考前面的文章JavaScript 之继承的多种方式和优缺点,这里主要说说 ES6 中 Class 的继承。

Class 可以通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Parent {
constructor(name) {
this.name = name;
}
}

class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的 constructor(name)
this.age = age;
}
}

var child1 = new Child("kevin", "18");

console.log(child1);

值得注意的是:

super 关键字表示父类的构造函数,相当于 ES5 的 Parent.call(this)。
子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。
也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。

类的 prototype 属性和proto属性

在 ES5 实现之中,每一个对象都有__proto__属性,指向对应的构造函数的 prototype 属性。在 ES6 中,父类的静态方法,可以被子类继承,Class 作为构造函数的语法糖,同时有 prototype 属性和__proto__属性,因此同时存在两条继承链。

  1. 子类的__proto__属性,表示构造函数的继承,总是指向父类。
  2. 子类 prototype 属性的__proto__属性,表示方法的继承,总是指向父类的 prototype 属性。
1
2
3
4
5
6
7
8
9
10
11
class Foo {
static classMethod() {
return "hello";
}
}

class Bar extends Foo {}

Bar.classMethod(); // 'hello'
console.log(Child.__proto__ === Parent); // true
console.log(Child.prototype.__proto__ === Parent.prototype); // true

ES6 的原型链示意图为:

(原型图)

我们会发现,相比寄生组合式继承,ES6 的 class 多了一个 Object.setPrototypeOf(Child, Parent) 的步骤。

Object.setPrototypeOf 等同于:

1
2
3
4
Object.setPrototypeOf = function (obj, proto) {
obj.__proto__ = proto;
return obj;
};

Object.getPrototypeOf()

Object.getPrototypeOf 方法可以用来从子类上获取父类。

1
2
Object.getPrototypeOf(Child) === Parent;
// true

因此,可以使用这个方法判断,一个类是否继承了另一个类。

Babel 编译

先看这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Parent {
constructor(name) {
this.name = name;
}
}

class Child extends Parent {
constructor(name, age) {
super(name); // 调用父类的 constructor(name)
this.age = age;
}
}

var child1 = new Child("kevin", "18");

console.log(child1);

Babel 编译后为:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"use strict";

function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function _typeof(obj) {
return typeof obj;
};
} else {
_typeof = function _typeof(obj) {
return obj &&
typeof Symbol === "function" &&
obj.constructor === Symbol &&
obj !== Symbol.prototype
? "symbol"
: typeof obj;
};
}
return _typeof(obj);
}

function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: { value: subClass, writable: true, configurable: true },
});
if (superClass) _setPrototypeOf(subClass, superClass);
}

function _setPrototypeOf(o, p) {
_setPrototypeOf =
Object.setPrototypeOf ||
function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}

function _createSuper(Derived) {
var hasNativeReflectConstruct = _isNativeReflectConstruct();
return function _createSuperInternal() {
var Super = _getPrototypeOf(Derived),
result;
if (hasNativeReflectConstruct) {
var NewTarget = _getPrototypeOf(this).constructor;
result = Reflect.construct(Super, arguments, NewTarget);
} else {
result = Super.apply(this, arguments);
}
return _possibleConstructorReturn(this, result);
};
}

function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
} else if (call !== void 0) {
throw new TypeError(
"Derived constructors may only return object or undefined"
);
}
return _assertThisInitialized(self);
}

function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError(
"this hasn't been initialised - super() hasn't been called"
);
}
return self;
}

function _isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
Boolean.prototype.valueOf.call(
Reflect.construct(Boolean, [], function () {})
);
return true;
} catch (e) {
return false;
}
}

function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf
? Object.getPrototypeOf
: function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}

function _instanceof(left, right) {
if (
right != null &&
typeof Symbol !== "undefined" &&
right[Symbol.hasInstance]
) {
return !!right[Symbol.hasInstance](left);
} else {
return left instanceof right;
}
}

function _classCallCheck(instance, Constructor) {
if (!_instanceof(instance, Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}

var Parent = function Parent(name) {
_classCallCheck(this, Parent);

this.name = name;
};

var Child = /*#__PURE__*/ (function (_Parent) {
_inherits(Child, _Parent);

var _super = _createSuper(Child);

function Child(name, age) {
var _this;

_classCallCheck(this, Child);

_this = _super.call(this, name); // 调用父类的 constructor(name)

_this.age = age;
return _this;
}

return Child;
})(Parent);

var child1 = new Child("kevin", "18");
console.log(child1);

_inherits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function _inherits(subClass, superClass) {
// extend 的继承目标必须是函数或者是 null
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
// 类似于 ES5 的寄生组合式继承,使用 Object.create,设置子类 prototype 属性的 __proto__ 属性指向父类的 prototype 属性
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true,
},
});
// 设置子类的 __proto__ 属性指向父类
if (superClass) _setPrototypeOf(subClass, superClass);
}

_createSuper

函数里返回一个_possibleConstructorReturn 方法:

1
2
3
4
5
_createSuper(Child);
// 可以简化为:
_possibleConstructorReturn(this, Parent.call(this, name));
// 也就是说_this = _super.call(this, name);可以替换为:
_this = _possibleConstructorReturn(this, Parent.call(this, name));

_possibleConstructorReturn 的源码为:

1
2
3
4
5
6
7
8
9
10
function _possibleConstructorReturn(self, call) {
if (call && (_typeof(call) === "object" || typeof call === "function")) {
return call;
} else if (call !== void 0) {
throw new TypeError(
"Derived constructors may only return object or undefined"
);
}
return _assertThisInitialized(self);
}

在这里我们判断 Parent.call(this, name) 的返回值的类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 返回值: undefined
class Parent {
constructor() {
this.xxx = xxx;
}
}
// 返回值:Object
class Parent {
constructor() {
return {
name: "kevin",
};
}
}
// 返回值:null
class Parent {
constructor() {
return null;
}
}

所以对于 Parent.call(this) 的值,如果是 object 类型或者是 function 类型,就返回 Parent.call(this),如果是 null 或者基本类型的值或者是 undefined,都会返回 self 也就是子类的 this。

总结

最后总体看下如何实现继承:
首先执行 _inherits(Child, Parent),建立 Child 和 Parent 的原型链关系,即 Object.setPrototypeOf(Child.prototype, Parent.prototype) 和 Object.setPrototypeOf(Child, Parent)。

然后调用 Parent.call(this, name),根据 Parent 构造函数的返回值类型确定子类构造函数 this 的初始值 _this。

最终,根据子类构造函数,修改 _this 的值,然后返回该值。

参考文献