为什么Babel在将ES6类转译为ES5时使用Reflect.construct?

ckx4rj1h  于 2023-05-04  发布在  Babel
关注(0)|答案(1)|浏览(239)

为了支持旧的浏览器,我们都使用BabelJS将ES6转成ES5。当Babel编译一个扩展另一个类的类时,有一部分编译代码看起来像这样:

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

我知道_createSuper函数的目的是创建一个函数来 Package 调用超类的过程,它将this绑定为子类的接收者,以便子类可以继承其超类的属性。为了达到这个目的,我们只需要让result = Super.apply(this, arguments)。但是代码检查环境是否支持Reflect并优先使用Reflect.construct。实际上,我不知道为什么我们需要result = Reflect.construct(Super, arguments, NewTarget)以及它的作用。有人能详细解释一下吗?

bvjxkvbb

bvjxkvbb1#

对象构造(由Reflect.construct执行)和Function.prototype.apply的语义是不相同的。在ECMAScript 6之前,它们非常相似(至少对于用户代码函数),但也有一些明显的差异,这些差异只是随着时间的推移而积累起来的。机会性地使用Reflect.construct允许在可能的情况下应用真正的构造语义,而不是仅仅近似的函数调用语义。
对于一个简单的情况:原生ECMAScript 6类根本不能像函数那样调用(直接,通过Function.prototype.callFunction.prototype.apply);只能使用new操作符或Reflect.construct将它们作为构造函数调用。当一个转译类的基类恰好是一个原生的ECMAScript 6类时,使用Reflect.construct允许子类化(大部分)透明地发生。

// non-transpiled class

class Clbutt {
  constructor(x, y) {
    console.log(`Clbutt constructin’ a ${new.target?.name}`);
    this.x = x;
    this.y = y;
  }

  toString() {
    return `{ ${Object.entries(this)
      .map(([k, v]) => `${k}: ${v}`)
      .join(", ")} }`;
  }
}

// (simplified) transpiled class

function Subclbutt(x, y) {
  // this would have been a TypeError:
  // var ðis = Clbutt.apply(this, arguments);

  var ðis = Reflect.construct(Clbutt, arguments, this.constructor);
  console.log("Subclbutt constructin’");
  ðis.dist = Math.sqrt(x * x + y * y);
  return ðis;
}

// probably polyfilled using the non-standard __proto__ if absent
Object.setPrototypeOf(Subclbutt.prototype, Clbutt.prototype);
Object.setPrototypeOf(Subclbutt, Clbutt);

// user code

console.log(`${new Clbutt(3, 4)}`);
console.log(`${new Subclbutt(3, 4)}`);

当子类化某些内置类(如MapPromise)或DOM类(如Element)时,也会出现类似的问题;这些构造函数也不能作为函数调用。这并不是说内建子类首先是一个好主意,但是如果直接运行的代码可以做到这一点,那么编译的代码也应该可以。
事实上,在ECMAScript 6中,通过检查new.target是对象还是未定义,即使是普通函数也可以区分正常调用和作为构造函数调用。使用Reflect.construct代替Function.prototype.apply也消除了这种差异。

相关问题