【问题标题】:What are "class fields" in JavaScript?JavaScript 中的“类字段”是什么?
【发布时间】:2019-12-27 17:31:25
【问题描述】:

我在阅读 JavaScript 类时遇到了“公共类字段语法”这个术语。在深入挖掘它时,我遇到了这个Babel's documentation on class properties

有人可以解释一下 - 实现方面,这种新语法的用例是什么? (它为 JavaScript 提供了哪些解决方案/好处,到目前为止还没有这些解决方案/好处?)

下面是一个示例(在 Google Chrome 中运行没有错误)

class Person {
    firstName = "Mike";
    lastName = "Patel";
    
    // this is a public class field syntax
    getName = () => {
      return this.firstName + " " + this.lastName;
    };
}

var p = new Person();

console.log(p.firstName); // Mike
console.log(p.lastName); // Patel
console.log(p.getName); // () => { return this.firstName + " " + this.lastName; }
console.log(typeof p.getName); // function
console.log(p.getName()); // Mike Patel

【问题讨论】:

  • “特别”是什么意思?如果您希望这会做一些奇怪且不直观的事情 - 那么不,它完全按照您的想法做 - 它会创建两个属性。这只是一开始不可用的新语法。在 Firefox 68 中,这会抛出 SyntaxError: fields are not currently supported
  • @VLAZ 我在谷歌浏览器中检查过,它执行没有任何错误。
  • 这可能会有所帮助:medium.com/@jacobworrel/…
  • @AadityaSharma 再次出现在 Firefox 中。它是较新的语法,因此并未在任何地方完全实现。这就是重点。这是它唯一“特别”的地方,但我不知道这是否是你认为的“特别”。
  • @AadityaSharma 所以,不是“什么特别”,而是“用例是什么”。因为语法本身并不特殊。用例是您选择这个而不是其他代码的原因。

标签: javascript ecmascript-6 ecmascript-next class-fields


【解决方案1】:

简单地说,使用它的原因是易于理解代码。如果没有类字段声明,您将执行以下操作:

class Person {
  constructor() {
    this.firstName = "Mike";
    this.lastName = "Patel";

    this.getName = () => {
      return this.firstName + " " + this.lastName;
    };
  }
}

var p = new Person();

console.log(p.firstName); // Mike
console.log(p.lastName); // Patel
console.log(p.getName); // () => { return this.firstName + " " + this.lastName; }
console.log(typeof p.getName); // function
console.log(p.getName()); // Mike Patel

工作,但现在您将可调用的getName() 和其余的普通实例属性都收集在构造函数中。您可以拥有更多,这意味着您的类定义总体上看起来毫无意义:

class MyClass() {
  constructor(someArg) {
    this.foo1 = 1;
    this.foo2 = 2;
    this.foo3 = 3;
    this.foo4 = someArg;

    this.bar1 = () => {}
    this.bar2 = () => {}
    this.bar3 = () => {}
    this.bar4 = () => {}
  }
}

等等。同样,一切都在构造函数中。如果您有很多代码,则很难阅读什么是什么。如果构造函数接受任何参数,那么您将有额外的开销来跟踪这些参数。因此,它难以阅读,难以维护,这一切都没有真正的好处。你把所有东西都放在同一个地方。

使用类字段声明,您将它们分开并得到

class MyClass() {
  /* properties - do not depend on the constructor*/
  foo1 = 1;
  foo2 = 2;
  foo3 = 3;
  foo4; /* this is a property that this class will have - 
          I do not need to look at the constructor to know about it */

  /* easy to see what the constructor does that is only about *constructing* the object */
  constructor(someArg) {
    this.foo4= someArg;
  }

  /* callable field are separated from the rest of the simple properties and construction logic */
  bar1 = () => {}
  bar2 = () => {}
  bar3 = () => {}
  bar4 = () => {}
}

所以,总而言之,它不是革命性的,但它的语法稍微好一点,可以更容易地表达一个类的内容。

【讨论】:

  • 迟到 JS 游戏有一个关于这个主题的问题,了解类字段建议旨在简化构造函数方法。这是我们推荐使用的东西吗?
  • @TommyLeong 我会说是的。我认为在构造函数中声明所有内容并没有真正的好处。这两种方法要么相等,要么类字段更优。
  • 所以这个/(其他)提案是否通过第4阶段并不重要,我们可以使用它吗?使用它们没有问题吗?也是 TC39 的新手。
  • @TommyLeong 如果您确信您运行代码的环境支持类字段,或者至少您使用 Babel,那么应该没有问题。
  • @Bergi 非常感谢。我确实对我使用的术语很草率。我重新措辞以避免混淆。
【解决方案2】:

引用class fields proposal

通过预先声明字段,类定义变得更加自文档化;实例经历较少的状态转换,因为声明的字段始终存在。

类字段的引入还允许私有类字段,这也带来了一些好处:

通过定义在类之外不可见的东西,ESnext 提供了更强大的封装,确保您的类的用户不会因依赖内部而意外绊倒自己,这可能会随着版本的变化而变化。

【讨论】:

    【解决方案3】:

    它源自this proposal,正在解决“问题”。

    预提案

    假设你想拥有一个类Foo,它包含一个默认属性,那么你可以直接编写如下

    class Foo {
      defaultAttribute = 'default';
      getDefault() { return this.defaultAttribute; }
    }
    

    请记住,上面的提案并未在 atm 中实施。设置类对象(编译时)时,defaultAttribute = '...' 被忽略。它甚至不是原型或字段成员(函数对象)的一部分。 那是因为defaultAttribute 没有被编译器拾取。因此你不能做foo.defaultAttribute

    调用getDefault() 会在这里抛出一个错误,因为此时它是未定义的。如果您在构造函数中为defaultAttribute 提供值,则该函数确实有效;

    class Foo {
      defaultAttribute = 'default';
      constructor() {
        this.defaultAttribute = 'hello';
      }
      getDefault() { return this.defaultAttribute; }
    }
    

    在这种情况下,defaultAttribute 设置为“Hello”,但它确实没有'default' 覆盖原始变量。

    提案后

    通过该提议,“忽略”问题得到解决,您可以按照您刚才描述的方式进行操作:

    class Foo {
      defaultAttribute = 'default';
      getDefault() { return this.defaultAttribute; }
    }
    

    有了这个,你可以跳过constructor()的使用

    class Foo1 {
      defaultAttribute = 'default';
      getDefault() { return this.defaultAttribute; }
    }
    
    class Foo2 {
      defaultAttribute = 'default';
      constructor() {this.defaultAttribute = 'hello';}
      getDefault() { return this.defaultAttribute; }
    }
    
    const foo1 = new Foo1();
    const foo2 = new Foo2();
    
    console.log(foo1.getDefault());
    console.log(foo2.getDefault());

    【讨论】:

    • "defaultAttribute = '...' 在设置类对象时被忽略(编译时)" - 不,不是。在提案实施之前,直观地写一下,只会导致语法错误。 “调用getDefault() 会在此处抛出错误,因为此时它是未定义的。” - 不。在 JS 中访问不存在的属性只返回undefined,不会抛出错误。
    猜你喜欢
    • 2021-01-26
    • 2012-05-04
    • 1970-01-01
    • 1970-01-01
    • 2011-02-18
    • 2018-12-25
    • 2019-07-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多