从babel编译后代码看class和继承

1,083 阅读2分钟

本文从 babel 编译后的代码分析理解 class 的特性和继承。

前置知识

  • Object.setPrototypeOf(obj, prototype) / Object.getPrototypeOf(obj)

设定 obj 的原型对象为 prototype / 获取 obj 的原型

  • Object.create(proto [, propertiesObject])

创建原型对象为 proto 的对象

// 简易版没加限制
Object.create = function(proto){
    function F(){}
    F.prototype = proto;
    return new F();
}
  • Object.defineProperty

对象上添加新属性

  • Reflect.construct(target, args [, newTarget])

相当于 new target(...args),返回的是 target 构造函数,原型指向 newTarget

注意:函数直接执行 new.targetundefined,new 调用有值,Reflect 会自动指向。

例如

function OneClass() {
    console.log('OneClass');
    console.log(new.target);
}
OneClass.prototype.sayOne = function () { console.log('sayOne') }
function OtherClass() {
    console.log('OtherClass');
    console.log(new.target);
}
OtherClass.prototype.sayOther = function () { console.log('sayOther') }

var obj2 = Reflect.construct(OneClass, [], OtherClass);
// OneClass function OtherClass { ... }

// 等效
var obj3 = Object.create(OtherClass.prototype);
OneClass.apply(obj3, []);
// OneClas undefined

class 编译后是啥

class Parent {
  p = 'parent';
  hiP = () => {}
  
  constructor(){
    this.age = 10;
  }
  
  sayParent(){}
  
  static hi(){}
}

转换后

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

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

var Parent = /*#__PURE__*/ (function () {
  "use strict";
  function Parent() {
    _classCallCheck(this, Parent);

    _defineProperty(this, "p", "parent");

    _defineProperty(this, "hiP", function () {});

    this.age = 10;
  }

  _createClass(
    Parent,
    [
      {
        key: "sayParent",
        value: function sayParent() {}
      }
    ],
    [
      {
        key: "hi",
        value: function hi() {}
      }
    ]
  );

  return Parent;
})();

执行流程:

image.png

可以看出 class 编译成一个立即函数,执行时,初始化一个 Parent 函数,调用 _createClass,分别往构造函数(static xx)和原型上添加方法(定义在class 内的方法),最后返回 Parent 函数。

new Parent() 时,执行 Parent 函数,内部会往实例上添加属性。

ES6 class 特性

  • 必须使用 new 调用;

  • 不存在变量提升;

  • 默认即是严格模式;

  • 内部所有定义的方法都是不可枚举的;

  • 子类继承父类,默认构造函数中会调用父类,显示声明 constructor 时,要有 super();

class 经 babel 编译后就是函数表达式

class Parent {}

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

var Parent = function Parent() {
  "use strict";

  _classCallCheck(this, Parent);
};

很容易看出:默认是严格模式必须 new 调用 ,当直接函数调用 Parent(),会报错。

class 不能提升 的目的是保证继承的有效性,由于 class 编译后就是函数表达式,如下代码会出错,

new Parent();
class Parent {}

new Parent();
var Parent = ...;

先执行 new ,发现 Parent 未定义,报错;

class Parent {}

class Child extends Parent {
}

var Parent = ...;
var Child = ...;

现在来看,假设 class 会提升,当执行到 Child 时,Child 到 Parent 前面,找不到 Parent 报错。

内部所有定义的方法都是不可枚举的

class Parent {
  sayParent(){}
  static hi(){}
}
Object.keys(Parent) // []
Object.keys(Parent.prototype) // []

// bable 编译后的关键代码也能看出
function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    descriptor.enumerable = descriptor.enumerable || false; // 这里
    descriptor.configurable = true;
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

继承 extends

// 源
class Parent {
  p = 'parent';
  hiP = () => {}
  
  constructor(){
    this.age = 10;
  }
  
  sayParent(){}
  
  static hi(){}
}

class Child extends Parent{

}
// 转换
var Child = /*#__PURE__*/ (function (_Parent) {
  "use strict";
	
  _inherits(Child, _Parent);

  var _super = _createSuper(Child);

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

    return _super.apply(this, arguments);
  }

  return Child;
})(Parent);

// 继承的关键代码
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);
}

Child 初始化时,通过原型式继承,Child.prototype 原型对象为 Parent.prototype,Child 的原型对象为 Parent,这样,Child 能访问到 Parent 上的静态方法,new Child() 的实例能访问 Parent.prototype 对象上的方法属性。

_createSuper 返回一个函数:

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

new Child ,执行该方法,Derived 为 Child ,所以 Super 为 Parent,返回 Parent 实例

以上是子类没有声明 constructor 时,会默认调父类

  function Child() {
    _classCallCheck(this, Child);
		// 调父类
    return _super.apply(this, arguments);
  }

当子类有 constuctor 时,会怎么样呢?

  • 没写 super() ,会报错,提示 this hasn't been initialised - super() hasn't been called
// 源
class Child extends Parent{
    constructor(){}
}
// 转换
var Child = /*#__PURE__*/ (function (_Parent) {
  "use strict";

  _inherits(Child, _Parent);

  var _super = _createSuper(Child);

  function Child() {
    var _this;

    _classCallCheck(this, Child);

    return _possibleConstructorReturn(_this);
  }

  return Child;
})(Parent);
// call 为 undefined 走 _assertThisInitialized
function _possibleConstructorReturn(self, call) {
  if (call && (_typeof(call) === "object" || typeof call === "function")) {
    return call;
  } else if (call !== void 0) {
    throw new TypeError(
      "Derived constructors may only return object or undefined"
    );
  }
  return _assertThisInitialized(self);
}
// self 为 undefined
function _assertThisInitialized(self) {
  if (self === void 0) {
    throw new ReferenceError(
      "this hasn't been initialised - super() hasn't been called"
    );
  }
  return self;
}
  • 有 super() 时,编译如下,和没有声明 constructor 类似
// 源
class Child extends Parent{
    constructor(){
      super();
    }
}
// 转换
var Child = /*#__PURE__*/ (function (_Parent) {
  "use strict";
  _inherits(Child, _Parent);

  var _super = _createSuper(Child);

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

    return _super.call(this);
  }

  return Child;
})(Parent);
  • this 在 super() 前
class Child extends Parent{
    constructor(){
      this.child = 'dxx';
      super();
      
    }
}

var Child = /*#__PURE__*/ (function (_Parent) {
  "use strict";
  _inherits(Child, _Parent);
  var _super = _createSuper(Child);

  function Child() {
    var _this;

    _classCallCheck(this, Child);

    _this.child = "dxx";
    return (_this = _super.call(this));
  }

  return Child;
})(Parent);

new Child() _this 为 undefined,到 _this.child = "dxx" 报错

  • this 在 super() 后
class Child extends Parent{
    constructor(){
      super();
      this.child = 'dxx';
    }
}

var Child = /*#__PURE__*/ (function (_Parent) {
	// ...
  function Child() {
    var _this;

    _classCallCheck(this, Child);

    _this = _super.call(this);
    _this.child = "dxx";
    return _this;
  }

  return Child;
})(Parent);

证明 Child 实例 _this ,由父类产生。

总结

我们从 babel 编译后的代码看到 class 是语法糖,实际就是函数表达式。当子类 extends 父类,帮我们实现了继承,注意构造函数中使用 this,要在 super() 调用后。

参考

MDN