class继承

130 阅读4分钟

ES5 中的类是通过 构造函数 + 原型对象 来实现的,如下:

function Point(x, y) {
    this.x = x;
    this.y = y;
}
Point.prototype.add = function() {
    console.log(this.x + this.y);
}
let p = new Point(1, 2);

一、class 语法

class 只是一个语法糖,大部分功能 ES5 也能实现。

  • 写法:(1)class (2)constructor(3)直接方法名。
  • 调用:类可以看作构造函数的另一种写法(大部分行为也与ES5一致),和构造函数一样用 new,并且 typeof Point === 'function'
class Point {
  constructor(x, y) {  // 构造方法
      this.x = x;
      this.y = y;
  }
  // 方法之间不需要逗号分隔
  add() {  // 无 function 前缀
      console.log(this.x + this.y);
  }
}
let p = new Point(1, 2);
p.add();
// 3

注意,类的所有方法都定义在类的 prototype 属性上面,包括 constructor。

// 调用实例p的方法,实际上就是调用原型上的方法
p.constructor === Point.prototype.constructor  // true
// 原型的 constructor 指向构造函数
Point.prototype.constructor === Point  // true

1. constructor

constructor 是类的默认方法,new 命令自动调用,如果没有显式定义,会默认添加一个空的 constructor 方法。

class Point {
    constructor() {}
}

另外,constructor 默认返回实例对象,因此也可以指定返回另一个对象。

2. 类的一些注意事项

  • 和普通构造函数的区别:类必须 new 调用、否则报错,但普通构造函数不用 new 也可以执行(此时会变成了普通函数,不会生成实例对象,并且内部的this指向全局对象)。类的所有方法都是不可枚举的。

  • 类和模块的内部,默认就是严格模式,不用 use strict 声明。

  • 不存在变量提升,所以不能先使用后定义。

  • 如果某个方法前有 *,就表示该方法是一个 Genarator 函数。

  • 类的方法内部的this,默认指向类的实例但如果该方法单独使用,this会指向 undefined(因为严格模式)

class Point {
  constructor(x, y) { 
      this.x = x;
      this.y = y;
  }
  add() {  
      console.log(this.x + this.y);
  }
}
let p = new Point(1, 2);
add();  // Error:add 没有定义

有两种解决方法,绑定this

// 在构造函数中使用 bind 绑定
this.add = this.add.bind(this);
// 在构造函数中使用 箭头函数
this.add = () => this

3. 静态方法

方法前加上 static,表示 (1)该方法不会被实例继承,(2)通过类直接调用。(3)如果静态方法中包含this,那么this指向类。(4)父类的静态方法,可以被子类继承

4. new.target

ES6 为new命令引入了 new.target 属性,用于构造函数中,返回new命令作用于的那个构造函数,来确定构造函数是怎么调用的。

二、class 继承

1. extends 继承

class Point {  // 父类方法
  constructor(x, y) {  
      this.x = x;
      this.y = y;
  }
  add() { 
      return this.x + this.y;  // 要用return返回
  }
}
class NumPoint extends Point {
  constructor(x, y, num) {
    super(x, y);  // 父类
    this.num = num;
  }
  add() {
    console.log(super.add() + this.num);  // 父类
  }
}
let p = new NumPoint(2, 2, 3);
p.add()
// 7

子类必须先在 constructor 方法中调用 super()

为什么子类的构造函数,一定要调用`super()`ES5的继承机制是先生成子类的实例对象,再将父类的方法添加到这个对象上。
而ES6则是先将父类的属性、方法加到一个空对象上,然后再将该对象作为子类的实例。

super() 在这里表示父类的构造函数,调用后会运行父类构造函数,生成继承父类的this对象。(父类构造函数)

(类只是将构造函数和原型对象做了一层封装,constructor还是构造函数,其他方法还是定义在原型上的方法)

2. super()

1. super 当作函数使用

此时 super 代表父类的constructor构造函数只能用在子类的构造函数中。类继承时,子类的构造函数必须执行一次 super 函数

class A {}
class B extends A {
    constructor() {
        super();
    }
}

super虽然表示父类构造函数,但返回的是子类实例,内部的this指向的是子类实例

2. super 当作对象使用

  1. 在子类普通方法中,指向父类的原型对象。所以定义在父类constructor上的属性或方法,都不能通过super调用。而除了父类constructor构造方法之外的其他方法,都是父类原型对象上的方法,可以通过super调用。
class Parent {
  add() {
    return 2;
  }
}
let p = new Parent();
Parent.constructor.prototype === p.add.__proto__   // true
Parent.constructor === p.add.__proto__.constructor   // true

另外,在子类普通方法通过super调用父类方法时,方法内部的this指向当前的子类实例

class Parent {
  constructor() {
    this.x = 1
  }
  add() {
    console.log(this.x);
  }
}
class Child extends Parent {
  constructor() {
    super();
    this.x = 2;
  }
  print() {
    super.add();  // super中的this执行当前实例
  }
}
let ch = new Child();
ch.print();  // 2
  1. 在子类静态方法中,super将指向父类。另外,在子类的静态方法通过super调用父类方法时,方法内部的this指向当前的子类,即子类的constructor(不是子类实例)

3. 类的prototype 和 __proto__ 属性

在ES5 中,每个实例对应的 __proto__ 都指向构造函数的 prototype 属性。而在Class 同时有 prototype__proto__ 两条继承链。

  • 子类__proto__指向父类。
  • 子类的prototype属性__proto__指向父类的prototype属性。
Child.__proto__ === Parent   // true
Child.prototype.__proto__ === Parent.prototype  // true

image.png

类的继承是按照下面的模式实现的:

class Parent {}
class Child {}
// 方法继承,子类的实例继承父类的实例
Object.setPrototypeOf(Child.prototype, Parent.prototype);
// 构造函数继承,子类继承父类的静态属性
Object.setPrototypeOf(Child, Parent);
  • 子类的__proto__属性,表示构造函数的继承,指向父类。
  • 子类的prototype属性的__proto__属性,表示方法的继承,指向父类的prototype属性。