【babel】babel如何编译class与extends

2023/02/15 10:20:42

示例

Foo 类拥有这些属性:

  • 两个成员变量 agename
  • 一个成员方法 getInfo()
  • 一个静态变量 skin
  • 一个静态方法 getSkin()
class Foo {
  constructor() {
    this.name = "zkb";
  }

  age = 22;

  static skin = "black";

  getInfo() {
    return this.name + this.age;
  }

  static getSkin() {
    return Foo.skin;
  }
}

babel 编译结果

对于 Foo 类,babel 的编译结果如下(删除了类型转换相关代码):

"use strict";

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, _toPropertyKey(descriptor.key), descriptor);
  }
}
function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, "prototype", { writable: false });
  return Constructor;
}
function _defineProperty(obj, key, value) {
  key = _toPropertyKey(key);
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true,
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

var Foo = /*#__PURE__*/ (function () {
  function Foo() {
    _classCallCheck(this, Foo);
    _defineProperty(this, "age", 22);
    this.name = "zkb";
  }
  _createClass(
    Foo,
    [
      {
        key: "getInfo",
        value: function getInfo() {
          return this.name + this.age;
        },
      },
    ],
    [
      {
        key: "getSkin",
        value: function getSkin() {
          return Foo.skin;
        },
      },
    ]
  );
  return Foo;
})();
_defineProperty(Foo, "skin", "black");

babel 对 class 的实现

先看编译后的最后一段代码:

var Foo = /*#__PURE__*/ (function () {
  function Foo() {
    _classCallCheck(this, Foo);
    _defineProperty(this, "age", 22);
    this.name = "zkb";
  }
  _createClass(
    Foo,
    [
      {
        key: "getInfo",
        value: function getInfo() {
          return this.name + this.age;
        },
      },
    ],
    [
      {
        key: "getSkin",
        value: function getSkin() {
          return Foo.skin;
        },
      },
    ]
  );
  return Foo;
})();
_defineProperty(Foo, "skin", "black");

可以看出最终的 Foo 是一个函数,这个函数内部调用了 _classCallCheck(this, Foo)_defineProperty(this, "age", 22) 两个方法,并且包含了 constructor() 函数的内容。

_classCallCheck(this, Foo) 的逻辑为:如果调用者(this)不是 Foo 的实例,则抛出异常,这主要是为了阻止非 Foo 实例来调用 Foo 方法。

Foo.call({}); // 抛出异常:Cannot call a class as a function

const foo = new Foo();
Foo.call(foo); // 正常执行

绑定成员变量

_defineProperty(this, "age", 22)this 绑定属性,逻辑为:如果 this 上有 arg 则用 Object.defineProperty() 设置其值,否则直接赋值。

这样处理的目的是:如果有 Foo 的实例将 age 属性设置为不可写,那么在调用 Foo.call(foo) 时就会将 age 属性重新设置为可写(但是如果手动将 age 属性设置为不可配置则会在这一步抛出异常)。

function _defineProperty(obj, key, value) {
  key = _toPropertyKey(key); // 将 key 转换为原始类型
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true,
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

const foo = new Foo();

// 设置 foo.age 为无法写入
Object.defineProperty(foo, "age", {
  value: 999,
  writable: false,
});

// 此时 foo.age 已无法修改
foo.age = 888;
console.log(foo.age); // 999

// 重新初始化后可修改
Foo.call(foo);
foo.age = 888;
console.log(foo.age); // 888

// 设置 foo.age 为无法配置
Object.defineProperty(foo, "age", {
  value: 999,
  configurable: false,
});
Foo.call(foo); // 抛出异常:Uncaught TypeError: Cannot redefine property: age

绑定静态变量

在初始化完毕 Foo 后调用 _defineProperty(Foo, "skin", "black") 来为 Foo 绑定静态变量,其实就是给方法设置一个属性值。

var Foo = /*#__PURE__*/ (function () {
  // ...
})();
_defineProperty(Foo, "skin", "black");

绑定成员方法和静态方法

在内部 Foo 方法中调用 _createClass() 来给 Foo 绑定成员方法和静态方法:

_createClass(
  Foo,
  [
    {
      key: "getInfo",
      value: function getInfo() {
        return this.name + this.age;
      },
    },
  ],
  [
    {
      key: "getSkin",
      value: function getSkin() {
        return Foo.skin;
      },
    },
  ]
);

_createClass() 方法的定义如下:

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  if (staticProps) _defineProperties(Constructor, staticProps);
  Object.defineProperty(Constructor, "prototype", { writable: false });
  return Constructor;
}

逻辑很简单:成员方法绑定到 Foo.prototype 上,静态方法绑定到 Foo 上。

babel 对 extends 的实现

在示例中添加一个类 Dto 来继承 Foo

class Dto extends Foo {
  constructor() {
    super();
    this.name = "zkb";
  }
}

编译结果中会在最后加入如下代码:

var Dto = /*#__PURE__*/ (function (_Foo) {
  _inherits(Dto, _Foo);
  var _super = _createSuper(Dto);
  function Dto() {
    var _this;
    _classCallCheck(this, Dto);
    _this = _super.call(this);
    _this.name = "zkb";
    return _this;
  }
  return _createClass(Dto);
})(Foo);

可以看出 Dto 的内部初始化流程和 Foo 类似,只是增加了 _inherits(Dto, _Foo)_super.call(this) 的调用。

继承父类

不难看出 _inherits(Dto, _Foo) 方法完成了继承的功能,_inherits() 方法定义如下:

// 设置对象原型
function _setPrototypeOf(o, p) {
  _setPrototypeOf = Object.setPrototypeOf
    ? Object.setPrototypeOf.bind()
    : function _setPrototypeOf(o, p) {
        o.__proto__ = p;
        return o;
      };
  return _setPrototypeOf(o, p);
}

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 },
  });
  Object.defineProperty(subClass, "prototype", { writable: false });
  if (superClass) _setPrototypeOf(subClass, superClass);
}

_inherits() 中抛去异常判断逻辑之外干了这几件事:

  • Dto.prototype 指向 Object.create(superClass.prototype), 继承 Foo 的原型方法。
  • Dto.prototype.constructor 指向 Dto
  • Dto.__proto__ 指向 Foo,继承 Foo 的静态方法。

super()

编译后的代码用 _createSuper() 方法创建了 _super,定义如下:

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

这里的 if 语句只是区分了调用 Super 的方式,因此不需要关注 Reflect 相关的内容,只关注 Super.apply() 即可,两种方式是等价的。

_possibleConstructorReturn(this, result) 是检测方法,如果有问题则抛出异常,否则返回 this

关键代码:

  • var Super = _getPrototypeOf(Derived)

    _inherits() 方法中已经将 Dto 的原型指向为 Foo,所以这里定义的 Super 其实就是 Foo 函数本身。

  • result = Super.apply(this, arguments)

    相当于 Foo.apply(this, arguments)

总结

class 降级编译之后用原型模式来实现类:

function Foo() {
  // 函数体相当于 constructor
  this.name = "zkb"; // this 上绑定的相当于成员属性
}

// 成员变量、成员方法绑定在实例的 __proto__ 也就是 Foo.prototype 上
Foo.prototype.age = 22;
Foo.prototype = function getInfo() {
  return this.name + this.age;
};

// 静态变量、静态方法绑在 Foo 上
Foo.skin = "black";
Foo.getSkin = function () {
  return Foo.skin;
};

extends 降级编译之后是原型继承的模式:

function Foo() {}
function Dto() {
  Dto.__proto__.apply(this, arguments);
}

Dto.prototype = Object.create(Foo.prototype);
Dto.prototype.constructor = Dto;
Dto.__proto__ = Foo;

Q&A

为什么要做 A.prototype.constructor=A 这样的修正?

constructor 其实没有什么用处,只是 JavaScript 语言设计的历史遗留物。

由于 constructor 属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。

不过,从编程习惯上,我们应该尽量让对象的 constructor 指向其构造函数,以维持这个惯例。

参考自:js 实现继承的方法中为何总是要修正 constructor 方法的指向呢?open in new window