# 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当作对象来使用
    }
}
1
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# super关键字

  1. 在上面的例子中super关键字表示父类的构造函数,新建父类的this对象。相当于Point.prototype.constructor.call(this)
  2. 子类没有this对象,所以必须在子类的构造方法中调用super方法,达到继承父类this对象的作用。
  3. 在子类的构造函数中,只有调用super之后才可以使用this关键字,否则会报错。
  4. 这个关键字可以当对象使用,也可以当函数使用,两种使用效果截然不同。第一点就是作为函数使用的情况;
  5. super()内部的this指向的是子类。作为函数时,super()只能用在子类的构造函数中,用在其他地方会报错。
  6. 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  1. 由于super作为对象使用时指向父类的原型对象,所以定义在父类实例上的方法或属性是无法通过super调用的,即构造方法内的。如果定义在父类的原型对象上,就可以取到
class A {
    constructor() {
        this.p = 2;
    }
}
class B extends A {
    get m () {
        return super.p;
    }
}
let b = new B();
b.m // undefined
1
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()
1
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
1
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();
1
2
3
4
5
6
7
8
9

这两条继承链可以这样理解:作为一个对象,子类(B)的原型(__proto__属性)是父类A;作为一个构造函数,子类B的原型(prototype属性)是父类的实例。

# extends继承目标的特殊性

  1. 子类继承Object类。
class A extends Object {

}
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true
1
2
3
4
5

这种情况A其实就是构造函数Object的复制,A的实例就是Object的实例。

  1. 不存在任何继承。
class A {

}
A.__proto__ === Function.prototype // true
A.prototype.__proto === Object.prototype // true
1
2
3
4
5

A作为一个基类(不存在任何继承)就是一个普通函数,所以直接继承Function.prototype。但是A调用后返回一个空对象(即Object的实例)。

  1. 子类继承null
class A extends null {

}
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true
1
2
3
4
5

和第二种情况类似,只是A调用后返回的对象不继承任何方法,实际相当于执行了:

class C extends null {
    constructor() {
        return Object.create(null)
    }
}
1
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]
1
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()
    }
}
1
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);
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

TIP

Student函数为一个自执行函数,接收一个Person参数(就是要继承的父类)。返回一个构造函数Student