【问题标题】:Why are ES6 classes not hoisted?为什么 ES6 类没有被提升?
【发布时间】:2016-02-21 14:53:23
【问题描述】:

由于 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 类没有被提升?

【问题讨论】:

  • ES6 类只是语法糖,尽管它们主要是语法糖。
  • 吊装一直是造成误解和混乱的源头。 ES6 中添加的所有新声明结构(letconstclass)都是未提升的(嗯,它们是半提升的)。除非引用 Eich 或类似的内容,否则您不会得到不是有效推测的答案。
  • @mmm:MDN 是社区编辑的,有时会出错。不像其他网站那样经常,也不像其他网站那样经常,但有时。请参阅this answer 了解它们是如何被吊起和不被吊起的。
  • @PetrPeller:我认为他们认为变量、常量和类是错误的,是的,而且很可能是因为上面提到的 Bergi 等问题。我发现 functions 被提升的事实很有用,但我不知道他们会同意。它发生故障的地方是当您同时拥有被提升(函数 decls)和非提升(向它们或它们的 prototype 对象添加属性)的东西时。不过正常的功能,还是蛮好用的。
  • 这里的一个含义是你不能把“module.exports = MyClass”放在文件的顶部,然后再声明“class MyClass { ... }”。这行不通。我觉得这很不幸,因为我喜欢将“导出”放在顶部以使 API 易于看到。

标签: javascript ecmascript-6


【解决方案1】:

为什么 ES6 类没有被提升?

实际上它们提升了(变量绑定在整个范围内都可用),就像let and const are一样——它们只是没有被初始化。

提升它的定义是有意义的

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

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 类尚未初始化,则会引发错误。

【讨论】:

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

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

这种限制 [non-hoisting] 的原因是类可以有一个 extends 子句,其值为任意表达式。该表达式必须在正确的“位置”进行评估,它的评估不能被提升。

【讨论】:

    【解决方案3】:

    在 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 中有函数,你可以创建它的对象。

    【讨论】:

    • 虽然它有类似的效果,但事实并非如此。没有创建 IIFE 来评估类。 ecma-international.org/ecma-262/8.0/…
    • 如果我没记错的话,这是一个 iife 语法(函数 () {}())。如果您编译类,则将生成此代码。因为它被分配给 var 它不会被全局调用。取而代之的是它的自我调用并将值返回给 Foo。
    • 如果你使用像 babel 这样的东西,是的。但是原生支持类的环境不会这样做。
    • 类没有被提升.. 见这个页面。 developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes
    • let ... 也没有被提升
    【解决方案4】:

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

     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 extends Animal 被解释的时候,Animal 实际上是未定义的,我们得到一个错误。我们可以通过在 Dog 声明之前移动 Animal 表达式来轻松解决这个问题。 请参阅这篇关于该主题的精彩文章: https://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-10
      • 1970-01-01
      • 1970-01-01
      • 2015-06-02
      相关资源
      最近更新 更多