ES6 class 继承简析

使用最多的模式是组合继承:原型链继承共享的属性和方法,借用构造函数继承实例属性。

最简单的类

class A {}

Babel转换后

"use strict";

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

var A = function A() {
  _classCallCheck(this, A);
};

_classCallCheck()函数内部,判断this是否为Constructor的实例,目的是创建作用域安全的构造函数,防止构造函数被当成普通函数调用。例如A()调用,this会指向其他对象,于是_classCallCheck()函数抛出错误。

用函数关键字定义A,再把函数赋值给变量A。这个过程需要指出的是,函数关键字后的标识符A并非变量A,只能在函数内部引用。

用匿名函数赋值,而不是函数关键字定义函数,是为了让ES6 class不产生定义提前。

类的基本继承

class A {};
class B extends A {};

转换后

"use strict";

function _possibleConstructorReturn(self, call) {
  if (!self) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }
  return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

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

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

var A = function A() {
  _classCallCheck(this, A);
};

var B = function(_A) {
  _inherits(B, _A);

  function B() {
    _classCallCheck(this, B);

    return _possibleConstructorReturn(this, (B.__proto__ || Object.getPrototypeOf(B)).apply(this, arguments));
  }

  return B;
}(A);

_inherits()函数主要做了两件事:

  1. 让子类构造函数prototype的原型指向超类构造函数prototype,实现实例属性共享
  2. 让子类构造函数的原型指向超类构造函数,实现类属型方法共享

Object.create()方法,接受第二个参数用于重写属性描述。在babel转换的代码中,constructor属性被重新配置成了不可枚举。

Object.setPrototypeOf()方法,可直接将指定对象的原型设为另一个对象或者null。但该新特性属于ES6,使用时注意浏览器兼容性。

(B.__proto__ || Object.getPrototypeOf(B)).apply(this, arguments)此处借调超类的构造函数,完成子类实例的初始化。

jams-talk