【问题标题】:How does Babel.js create compile a class declaration into ES2015?Babel.js 如何创建将类声明编译成 ES2015?
【发布时间】:2016-06-16 22:35:41
【问题描述】:

我目前的任务是将 JavaScript 组件 ES5 转换为 ES6(使用 Babel.js 编译)。在使用类和 Babel.js 之前,我们进行了原型设计以从其他组件中获取函数。

com.company.js.ComponentA.prototype = new com.company.js.utils.UltraFunctions()

现在使用 Babel.js 并将 ComponentA 变成一个类时

class ComponentA {
  contructor(){
    this.property = "Proppy";
  }
  doStuff() {
    console.log("doStuff");
  }
}

当我在实例化这个组件后分析它时会发生什么,我现在看到了两个级别的原型。第一个原型拥有“属性”-第二个原型嵌套在第一个原型中,在这种情况下拥有所有功能“doStuff”。这带来了遗留组件的问题,这些组件不应该转换为类(还)。因为这些组件是通过二级原型放入的,所以它们覆盖了保存由 Babel.js 编译的“合成”类的功能的原型。

我不是在寻求解决方案。我只是想确定我对 Babel.js 将类转换为 ES5 JavaScript 的假设是否正确。尤其是上面提到的创建两级原型的事实。

更新

对不起,我误解了第一个原型!正如@T.J.Crowder 在 cmets 中所说,第一个是实例 - 因此“属性”被砸到实例中,而函数通过原型插入到“第一”级原型。所以,将我所说的一切替换为二级到一级,一级到实例。

【问题讨论】:

  • “当我在实例化这个组件后分析它时会发生什么,我现在看到了两层原型。” 不,只有一层。您看到的第一个级别是 instance,而不是它的原型,与旧的 function 样式“类”完全一样。
  • 我认为您需要显示更多上下文。使用new 为构造函数的prototype 属性创建对象,如您的“之前”示例所示,几乎总是一种反模式,因此,如果您可以展示更多您的内容重新做,也许可以解释为什么在那里使用new,并单独说明为什么你认为 Babel 的东西创建了多个级别的原型,这会很有用。
  • 对不起,我误解了第一个原型!正如@T.J.Crowder 所说,第一个是实例 - 因此“属性”被砸到实例中,同时通过原型设计将函数插入到“第一”级原型!
  • 我不明白你所说的“粉碎”是什么意思。 constructor 中的代码与旧式构造函数中的代码完全相同:将属性放在实例上。新语法对结果的内存布局引入了 no 更改。
  • 很抱歉使用“smashed”。我并不总是乐于使用务实的术语,所以我尝试用有趣的同义词来增加趣味。请给我一些时间来可视化我们使用的组件层次结构的差异以及 Babel.js 的结果如何与之相适应。但请记住,我不需要解决问题的方法。我只是想讨论一下 Babel.js 如何将一个类转换为 ES5 Javascript

标签: javascript class compilation babeljs


【解决方案1】:

ES6/Babel 类语法复制了与使用函数构造函数并覆盖原型时相同的原型模式。下面的例子一旦调用构造函数就会产生相同的实例对象。

ES6/巴别塔

class ComponentA {
      constructor(){
        this.property = "Proppy";
      }
      doStuff() {
        console.log("doStuff");
      }
    }

var c = new ComponentA();

ES5

var ComponentA = function () {
  this.property = "Proppy";
}

ComponentA.prototype.doStuff = function () {
  console.log("doStuff");
}

var c = new ComponentA();

下面是一个示例,说明如何在 ES6 类语法和 ES5 中继承另一个构造函数的原型。

ES6/巴别塔

class Parent {
  constructor(){
    this.property = "Proppy";
  }
  doStuff() {
    console.log("doStuff");
  }
}

class ComponentA extends Parent {
  constructor() {
    super();
  }
}

var c = new ComponentA();

ES5

var Parent = function () {
    this.property = "Proppy";
}

Parent.prototype.doStuff = function () {
    console.log("doStuff");
}

function ComponentA () {
  Parent.call(this);
}

ComponentA.prototype = Object.create(Parent.prototype);
ComponentA.prototype.constructor = ComponentA;

var c = new ComponentA();

在您给出的示例中,您的 ComponentA 没有以相同的方式继承 UltraFunctions() 的原型,因为您没有在覆盖原型后将 ComponentA.prototype.constructor 重置回 ComponentA 函数。

结果是一个 ComponentA 实例对象,它实际上不是 ComponentA 的实例,而是由 ComponentA 构造函数改变的 UltraFunctions() 实例。

您可能会得到一些负面结果,因为一旦您迁移组件以使用类语法,许多 Object.prototype 方法(如 .hasOwnProptery)将无法像以前那样工作。

您的示例的另一个问题是“构造函数”拼写错误。这将导致 Babel 中的结果与您的预期不同。

【讨论】:

  • 感谢您在这里提供的宝贵建议以及您花费的时间!我认为我真的需要重新考虑原型继承,因此我很高兴 Babel.js 可以通过“类”消除我的一些顾虑
【解决方案2】:

我只是想讨论一下 Babel.js 如何将一个类转换为 ES5 Javascript。

Babel 使用了很多辅助函数,或者我会说“只看转译的结果”。 :-)

在 ES2015 中,这是一个非常简单的映射,因为 class 语法在第一个版本中被刻意保持非常基本(ES2016 打算对其进行扩展,但提案¹并没有完全实现,所以它们会在以后,可能是 ES2017 ES2018 ES2021 或 ES2022)。

class 允许我们定义:

  • 构造函数(通过classconstructor
  • 构造函数的prototype对象的原型(通过extends
  • 将构造函数的prototype对象放入的方法
  • 构造函数本身的方法 (static)
  • 一种简洁且可移植地引用基“类”构造函数及其原型信息的方法

所以这个:

// Base "class":
class Base {
    // The code for `Base` goes in this special `constructor` pseudo-method:
    constructor() {
        this.baseProp = 42;
    }

    // A method to put on the `prototype` object (an "instance method"):
    baseMethod() {
        console.log(this.baseProp);
    }

    // A method to put on the constructor (a "static method"):
    static foo() {
        console.log("This is foo");
    }
}

// Derived "class":
class Derived extends Base {
//            ^------------------ defines the prototype behind `Derived.prototype`
    // The code for `Derived`:
    constructor() {
        // Call super constructor (`Base`) to initialize `Base`'s stuff:
        super();

        // Properties to initialize when called:
        this.derivedProp = "the answer";
    }

    // Overridden instance method:
    baseMethod() {
        // Supercall to `baseMethod`:
        super.baseMethod();

        // ...
        console.log("new stuff");
    }

    // Another instance method:
    derivedMethod() {
        this.baseMethod();
        console.log(this.derivedProp);
    }
}

变成我们在 ES5 中可能写的(如果我们不使用任何辅助函数),如下所示:

// This combines the name defined by `class` with the code defined in `constructor`:
var Base = function() {
    this.baseProp = 42;
};
// The "instance" method:
Base.prototype.baseMethod = function() {
    console.log(this.baseProp);
};
// The "static" method:
Base.foo = function() {
    console.log("This is foo");
};

// The derived constructor
var Derived = function() {
    // Call super constructor (`Base`) to initialize `Base`'s stuff:
    Base.call(this);

    // Properties to add when called:
    this.derivedProp = "the answer";
};

// This was done by `class` and `extends`:
Derived.prototype = Object.create(Base.prototype);
Derived.prototype.constructor = Derived;

// Overridden instance method:
Derived.prototype.baseMethod = function() {
    // Supercall to `baseMethod`:
    Base.prototype.baseMethod.call(this);

    // ...
    console.log(this.derivedProp);
};

// Another instance method:
Derived.prototype.derivedMethod = function() {
    this.baseMethod();
    console.log(this.derivedProp);
};

以上注意事项:

  • constructor 成为构造函数
  • 所有非constructor、非static的方法都成为原型方法
  • static 方法分配给构造函数的属性
  • 属性只是像往常一样的属性
  • 创建对象以放在派生构造函数的prototype 属性上是通过Object.create(Base.prototype) 完成的,不是 new Base()
  • constructor 调用基本构造函数作为其第一个操作。
  • 在 ES5 版本 (Base.prototype.baseMethod.call(this);) 中调用 super 的方法既麻烦又容易出错,这是新语法的一大优点

¹ 一些将显着扩展class 语法的提案:

截至 2021 年 1 月,V8(Chrome、Chromium、Brave、Node.js 等中的 JavaScript 引擎)支持上述所有内容。 SpiderMonkey(在 Firefox 等中)和 JavaScriptCore(在 Safari 中)也不甘落后。

【讨论】:

  • 感谢您的精彩回答 - 正是我想要开始的讨论!
  • 为什么在派生类的构造函数中使用this 之前需要调用这个super
  • @1252748 - 因为在允许子类开始初始化之前,需要初始化对象的超类方面。这有几个原因,尤其是如果子类可以“插队”,则无法保证超类的行为是一致的。
  • 我明白了。所以在你的答案中写// Supercall;Base.prototype.baseMethod.call(this);真的是super电话吗?或者super 调用不是Base.call(this)(即调用Base 的构造函数?另外,为什么必须将Derived 的构造函数设置为Derived.prototype.constructor = DerivedBase 的构造函数由它定义是函数体?最后,“构造函数调用基本构造函数作为它的第一个操作”=> 这是指Derived.prototype.constructor = Derived; 行吗?即,实际上调用构造函数?谢谢。
  • @1252748 - “Supercall”是“调用超类方法”(不是构造函数)的标准简写术语。 (我已经编辑澄清。)这些都是对超级baseMethod的调用。重新设置constructor,因为我们用别的东西替换了Derived.prototype用来指向的对象,所以JavaScript默认值constructor丢失了,我们不得不设置它。关于你的最后一个问题:不,这不会调用构造函数。 派生构造函数中的第一个操作是调用基构造函数(例如,ES2015 代码中的super(),ES5 代码中的Base.call(this))。
猜你喜欢
  • 2015-06-02
  • 2016-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-03
  • 1970-01-01
  • 2015-09-22
相关资源
最近更新 更多