# Class的继承
# 简介
不同于ES5利用原型实现继承的方式,ES6利用
extends
关键字实现继承。其实是寄生组合继承的语法糖
class ColorPoint extends Point { // extends 相当于Son.prototype = Object.create(Father.prototype)
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y) 相当于Father.call(this)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString() super当作对象来使用
}
}
2
3
4
5
6
7
8
9
10
这个例子就是ColorPoint
类继承Point
类。
TIP
- 子类只要继承父类,可以不写
constructor
,一旦写了第一句就是super
class Person {
constructor() {
this.type = 'person'
}
}
class Student extends Person {
constructor() {
super()
}
}
var student1 = new Student()
var person1 = new Person()
console.log(student1.type) // person
console.log(student1 instanceof Student) // true
console.log(student1 instanceof Person) // true
console.log(student1.hasOwnProperty('type')) // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# super关键字
- 在上面的例子中
super
关键字表示父类的构造函数,新建父类的this
对象。相当于Point.prototype.constructor.call(this)
- 子类没有
this
对象,所以必须在子类的构造方法中调用super
方法,达到继承父类this
对象的作用。 - 在子类的构造函数中,只有调用
super
之后才可以使用this
关键字,否则会报错。 - 这个关键字可以当对象使用,也可以当函数使用,两种使用效果截然不同。第一点就是作为函数使用的情况;
super()
内部的this
指向的是子类。作为函数时,super()
只能用在子类的构造函数中,用在其他地方会报错。super
作为对象时在普通方法中指向父类的原型对象;在静态方法中指向父类。
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance',msg)
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg); // super指向父类
}
myMethod(msg) {
super.myMethod(msg); //super指向父类原型对象
}
}
Child.myMethod(1) // staticc 1
var child = new Child();
child.myMethod(2); // instance 2
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 由于
super
作为对象使用时指向父类的原型对象,所以定义在父类实例上的方法或属性是无法通过super
调用的,即构造方法内的。如果定义在父类的原型对象上,就可以取到
class A {
constructor() {
this.p = 2;
}
}
class B extends A {
get m () {
return super.p;
}
}
let b = new B();
b.m // undefined
2
3
4
5
6
7
8
9
10
11
12
例子二:
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3; // 读取super.x时,相当于读取A.prototype.x,所以返回undefined
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
由于绑定子类的
this
,因此如果通过super
对象某个属性赋值,这时super
就是this
,赋值的属性变成子类实例的属性。
# ES5继承和ES6继承的区别
ES5的继承实质是先创造子类的实例对象
this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。ES6则是先创造父类的实例对象this
(使用super
),然后再用子类的构造函数修改this
。
# 类的prototype
属性和__proto__
属性
Class作为构造函数的语法糖,同时有
prototype
和__proto__
属性,因此同时存在两条继承链:
- 子类的
__proto__
属性表示构造函数的继承,总是指向父类 - 子类的
prototype
属性的__proto__
属性表示方法的继承,总是指向父类的prototype
属性。
class A {}
class B extends A {}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
2
3
4
造成上述两条继承链的原因是因为类的继承实现模式如下:
class A {}
class B {}
// B的实例继承A的实例
Object.setPrototypeOf(B.prototype,A.prototype);
// B的实例继承A的静态属性
Object.setPrototype(B, A);
const b = new B();
2
3
4
5
6
7
8
9
这两条继承链可以这样理解:作为一个对象,子类(B)的原型(
__proto__
属性)是父类A;作为一个构造函数,子类B的原型(prototype
属性)是父类的实例。
# extends
继承目标的特殊性
- 子类继承
Object
类。
class A extends Object {
}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
2
3
4
5
这种情况A其实就是构造函数
Object
的复制,A的实例就是Object
的实例。
- 不存在任何继承。
class A {
}
A.__proto__ === Function.prototype // true
A.prototype.__proto === Object.prototype // true
2
3
4
5
A作为一个基类(不存在任何继承)就是一个普通函数,所以直接继承
Function.prototype
。但是A调用后返回一个空对象(即Object
的实例)。
- 子类继承
null
。
class A extends null {
}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true
2
3
4
5
和第二种情况类似,只是A调用后返回的对象不继承任何方法,实际相当于执行了:
class C extends null {
constructor() {
return Object.create(null)
}
}
2
3
4
5
# 实例的__proto__
属性
子类实例的
__proto__
属性的__proto__
属性指向父类实例的__proto__
属性,也就是说子类的原型的原型是父类的原型。
# 原生构造函数的继承
TIP
ES6允许继承原生构造函数定义子类,因为ES6先创建父类的实例对象this
,然后再用子类的构造函数修饰this
,使得父类的所有行为都可以继承。所以extends
也可以用来继承原生的构造函数
class MyArray extends Array {
constructor(...args) {
super(...args)
}
}
var arr = new MyArray();
arr[0] = 12;
arr.length // 1
arr.length = 0;
arr[0] // undefined
var brr = new MyArray(1,2,3)
console.log(brr) // [1,2,3]
2
3
4
5
6
7
8
9
10
11
12
13
# ES6继承转码为ES5
class Person {
constructor() {
this.type = 'person'
}
}
class Student extends Person {
constructor() {
super()
}
}
2
3
4
5
6
7
8
9
10
11
利用babel
在线转码结果:
"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;
}
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 {
Date.prototype.toString.call(Reflect.construct(Date, [], 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 Person = function Person() {
_classCallCheck(this, Person);
this.type = 'person';
};
var Student = /*#__PURE__*/function (_Person) {
_inherits(Student, _Person);
var _super = _createSuper(Student);
function Student() {
_classCallCheck(this, Student);
return _super.call(this);
}
return Student;
}(Person);
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
TIP
Student
函数为一个自执行函数,接收一个Person
参数(就是要继承的父类)。返回一个构造函数Student
。