从babel看class(下)

519 阅读3分钟

书接上文从 babel 看 class(上),第一篇说了 class 法经过 babel 会转化成什么样子,而这篇则主要介绍继承。

es5

在 es5 下我们常用的就是寄生组合式继承了,下面就是一个例子,看下 es5 的实现步骤

function Foo() {
  this.name = "foo";
}
Foo.prototype.getName = function() {
  return this.name;
};
function Extends(name) {
  Foo.call(this);
  this.name = name;
}
Extends.prototype = Object.create(Foo.prototype);
Extends.prototype.construcotr = Extends;

var child = new Extends("Extends");
child.getName(); // "Extends"

上面对应的原型图为

back

es6

我们用 class 的形式来重写上面的例子

class Foo {
  name = "foo";
  getName() {
    return this.name;
  }
}
class Extends extends Foo {
  constructor(name) {
    super();
    this.name = name;
  }
}
var child = new Extends("Extends");
child.getName(); // "Extends"

class 通过 extends 实现继承,上面调用了super这是必须的,相当于Foo.call(this),如果没有调用就使用 this 会报错,因为子类的 this 必须通过父类的 this 来构建,extends 还可以继承父类的静态属性和方法

class Foo {
  name = "foo";
  getName() {
    return this.name;
  }
  static getName() {
    return "Foo";
  }
}
class Extends extends Foo {
  constructor(name) {
    super();
    this.name = name;
  }
  static get() {
    return super.getName();
  }
}
var child = new Extends("Extends");
console.log(Extends.get()); //Foo

child.getName(); // "Extends"

出现这种结果的原因是 class 存在两条继承链

  1. 子类的proto属性,表示构造函数的继承,总是指向父类;

  2. 子类 prototype 属性的proto属性,表示方法的继承,总是指向父类的 prototype 属性;

1

babel

babel 翻译之后代码有点多,我先精简一下只看核心部分

var Foo =
  /*#__PURE__*/
  (function() {
    function Foo() {
      _classCallCheck(this, Foo);

      _defineProperty(this, "name", "foo");
    }

    _createClass(
      Foo,
      [
        {
          key: "getName",
          value: function getName() {
            return this.name;
          }
        }
      ],
      [
        {
          key: "getName",
          value: function getName() {
            return "Foo";
          }
        }
      ]
    );

    return Foo;
  })();

var Extends =
  /*#__PURE__*/
  (function(_Foo) {
    _inherits(Extends, _Foo);

    function Extends(name) {
      var _this;

      _classCallCheck(this, Extends);

      _this = _possibleConstructorReturn(
        this,
        _getPrototypeOf(Extends).call(this)
      );
      _this.name = name;
      return _this;
    }

    _createClass(Extends, null, [
      {
        key: "get",
        value: function get() {
          return _get(_getPrototypeOf(Extends), "getName", this).call(this);
        }
      }
    ]);

    return Extends;
  })(Foo);

var child = new Extends("Extends");
console.log(Extends.get()); //Foo

child.getName(); // "Extends"

Foo这部分_classCallCheck_defineProperty_createClass在上一篇有介绍,主要就是给属性赋值,这里跳过,主要来看Extends部分

首先执行了_inherits函数,这个函数展开

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);
}

实际上就是验证和重写继承链,extends按照规范可以是一个函数或者 null,这里判断没什么好说的;

Object.setPrototypeOf如果存在就用这个,不存在直接用__proto__属性,重写了prototype和子类的原型。 再来看下这段代码

var _this;

_classCallCheck(this, Extends);

_this = _possibleConstructorReturn(this, _getPrototypeOf(Extends).call(this));

首先定义了一个_this的变量,还记得我们之前说 class 的继承是先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法,如果不使用super调用 this 进行赋值之类就会报错。

_possibleConstructorReturn函数就是通过判断_this是否为undefined来决定是否显示报错

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;
}

this, _getPrototypeOf(Extends).call(this);这段代码就相当于super从这一步 this 得到了父类同样的实例属性和方法,下面就是对子类进行加工。

总结

从整个执行过程来看,babel 的转化过程如下:

  1. 验证,如果不是 new 调用就报错,如果是继承还需要验证继承的是否为函数或者 null;
  2. 重写继承链,同时调用父类构造函数得到父类同样的实例属性和方法;
  3. 赋值,为构造函数本身和 this 赋值;

源代码太长就不放了,如果有兴趣可以自己打开babel 官网把上面代码复制进来,就可以看到完整代码