为什么不提升 ES6 类?

IT技术 javascript ecmascript-6
2021-01-23 11:09:49

由于 ES6 类只是JavaScript 现有的基于原型的继承 [1]语法糖,因此(IMO)提升它的定义是有意义的:

var foo = new Foo(1, 2); //this works

function Foo(x, y) {
   this.x = x;
   this.y = y;
}

但以下将不起作用:

var foo = new Foo(1, 2); //ReferenceError

class Foo {
   constructor(x, y) {
      this.x = x;
      this.y = y;
   }
}

为什么不提升 ES6 类?

4个回答

为什么不提升 ES6 类?

实际上,它们就像现在一样提升(变量绑定在整个范围内可用)- 它们只是没有初始化。letconst

提升其定义是有意义的

不。在定义之前使用类从来都不是一个好主意。考虑这个例子

var foo = new Bar(); // this appears to work
console.log(foo.x)   // but doesn't

function Bar(x) {
    this.x = x || Bar.defaultX;
}
Bar.defaultX = 0;

并将其与

var foo = new Bar(); // ReferenceError
console.log(foo.x);

class Bar {
    constructor (x = Bar.defaultX) {
        this.x = x;
    }
}
Bar.defaultX = 0;

正如您所料,这会引发错误。这是静态属性、原型混合、装饰器和所有东西的问题。这对于子类化也非常重要,当您使用具有未调整原型的类时,它在 ES5 中完全中断,但如果extended 类尚未初始化,则现在会抛出错误

我不确定我是否在给出的示例中看到了问题。如果Bar.defaultX = 0;放置在函数或类之外,那么我不希望它运行。也就是说,我也可以放在var foo = new Bar();类或函数声明之后但在默认赋值之前,并期望得到相同的结果,只是在类的情况下是有效代码。
2021-03-19 11:09:49
@BVernon 如果它是class Bar { static defaultX = 0 }(如公共类字段提案提供的),您是否也不会期望它运行它是相同的代码,只是脱糖了——例如,转译器会使用它。
2021-03-24 11:09:49
在你的第一个例子中澄清,问题是console.log(foo.x)会产生undefined而不是0不会出现运行时错误。
2021-03-29 11:09:49
@AurélienRibon 应该将类定义语句视为一个单元。您不会在其中放置不相关的实例化代码 :-) 关键是类定义的某些部分,例如静态属性值的创建、mixin 或装饰器调用以及超类表达式不能被提升。可以使用方法定义来提升class它的声明。
2021-03-29 11:09:49
您应该将该示例更改为更强大的示例。在你的class例子中,我仍然可以在类的定义和Bar.defaultX = 0赋值之间插入我的实例化+console.log ,并且日志仍然会打印 undefined :) 考虑使用一个简单的方法而不是静态属性,并在日志。方法在定义中class定义,而prototype在 ES5 版本中它们需要单独赋值。这似乎完全没有歧义。
2021-03-30 11:09:49

虽然非提升类(在某种意义上,它们的行为类似于let绑定)可以被认为是更可取的,因为它们导致更安全的使用(请参阅Bergi 的回答),但在2ality博客上找到的以下解释似乎提供了一个稍微更基本的原因执行:

这种限制[非提升]的原因是类可以有一个extends子句,它的值是一个任意表达式。该表达式必须在正确的“位置”中求值,它的求值不能被提升。

在 Javascript 中,所有声明(var、let、const、function、function*、class)都被提升,但应该在相同的范围内声明

正如你所说的“ES6 类只是 JavaScript 现有的基于原型的继承的语法糖”

那么让我们了解一下它是什么?

在这里,您声明了一个实际上是“特殊函数”的类。让我们假设您的函数 Foo() 和类 Foo 都在全局范围内。

class Foo {
   constructor(x, y) {
      this.x = x;
      this.y = y;
   }
}

以下是您的类 Foo 的编译代码。

var Foo = (function () {
    function Foo(x, y) {
        this.x = x;
        this.y = y;
    }
    return Foo;
}());

在内部,您的类被转换为包装函数(iife)内的同名函数,并且该包装函数返回您的函数。

因为您的函数的(类)范围已更改。并且您正在尝试在全局范围内创建实际上不存在的函数对象。

一旦编译完成,您就会在变量 Foo 中获得该函数。所以稍后你在 var 中有函数你可以创建它的对象。

如果你使用像 babel 这样的东西,是的。但是本机支持类的环境不会这样做。
2021-03-27 11:09:49
let ... 也没有被吊起
2021-04-01 11:09:49
2021-04-02 11:09:49
如果我没有错的话,这是一个 iife 语法(函数(){}())。如果你编译类,这个代码将被生成。因为它被分配给 var 它不会被全局调用。相反,它的自调用并将值返回给 Foo。
2021-04-05 11:09:49
虽然它具有类似的效果,但事实并非如此。没有创建 IIFE 来评估类。ecma-international.org/ecma-262/8.0/…
2021-04-11 11:09:49

类不会被提升,因为例如当一个类扩展一个表达式而不是一个函数时,会发生错误:

 class Dog extends Animal {}
 var Animal = function Animal() {
 this.move = function () {
 alert(defaultMove);
 }
 }
var defaultMove = "moving";
var dog = new Dog();
dog.move();

提升后这将变成:

var Animal, defaultMove, dog;
class Dog extends Animal {}
Animal = function Animal() {
this.move = function () {
alert(defaultMove);
}
}
defaultMove = "moving";
dog = new Dog();
dog.move();

在类 Dog 扩展 Animal 被解释为 Animal 实际上未定义的地方,我们得到一个错误。我们可以通过在 Dog 的声明之前移动 Animal 表达式来轻松解决这个问题。请看这篇关于该主题的精彩文章:https : //blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html