前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【译】ES提案:公有类字段

【译】ES提案:公有类字段

作者头像
腾讯IVWEB团队
发布2020-06-28 10:51:50
7970
发布2020-06-28 10:51:50
举报

原文链接https://2ality.com/2019/07/public-class-fields.html

这篇博客是关于类定义中新成员的系列文章中的一部分:

  1. 公有类字段
  2. 私有类字段
  3. 私有方法和JavaScript类的getter/setter方法

这个系列取代了2ality’s prior blog post on fields


在这篇文章中,我们将探讨公有类字段,一般用来创建实例熟悉和静态属性。此功能是Daniel Ehrenberg和Jeff Morrison的ES提案“JavaScript的类字段声明”的一部分。

  1. 概览 1.1. 公有实例字段const instFieldKey = Symbol('instFieldKey'); class MyClass { instField = 1; [instFieldKey] = 2; // computed key } const instance = new MyClass(); assert.equal(instance.instField, 1); assert.equal(instance[instFieldKey], 2);计算字段键类似于对象中的计算属性键。 1.2. 公有静态字段const staticFieldKey = Symbol('staticFieldKey'); class MyClass { static staticField = 1; static [staticFieldKey] = 2; // computed key } assert.equal(MyClass.staticField, 1); assert.equal(MyClass[staticFieldKey], 2);
  2. 公有实例字段和公有静态字段 2.1. 公有实例字段 设计公有字段的目的是这样的。 有时候,你会在构造函数中为一个实例属性赋值,但这个值不受构造函数中任何其他数据的影响(例如参数):class MyClass { constructor() { this.counter = 0; } } assert.equal(new MyClass().counter, 0);在这个示例中,你可以使用公有实例字段将计数器的创建移到构造函数的外面:class MyClass { counter = 0; constructor() { } } assert.equal(new MyClass().counter, 0);你还可以省略初始值的设定(=0)。在这种情况下,该属性会被初始化为undefined:class MyClass { counter; constructor() { } } assert.equal(new MyClass().counter, undefined);2.2. 公有静态字段 目前,在JavaScript中无法在类里面创建静态属性,你必须通过外部声明来创建它:class MyClass { } MyClass.prop = 123; assert.equal(MyClass.prop, 123);

一个解决方法是创建静态的getter:

代码语言:javascript
复制
class MyClass {
  static get prop() {
    return 123;
  }
}
assert.equal(MyClass.prop, 123);

而静态字段提供了更优雅的解决方案:

代码语言:javascript
复制
class MyClass {
  static prop = 123;
}
assert.equal(MyClass.prop, 123);
  1. 为什么命名为公有字段 公有字段创建的是属性。它们的名称为"字段",以强调它们与私有字段在语法上的相似程度(这是即将发布的博客文章的主题)

相似的,与私有字段相比,“公有”描述了公共字段的性质。

  1. 例子:使用字段替代构造函数 这是一个简短,更真实的例子,我们也可以用一个字段替换构造函数:class StringBuilder { constructor() { this.data = ''; } add(str) { this.data += str; return this; } toString() { return this.data; } } assert.equal( new StringBuilder().add('Hello').add(' world!').toString(), 'Hello world!');如果我们将.data的创建移到构造函数外面,我们将不再需要构造函数:class StringBuilder { data = ''; add(str) { this.data += str; return this; } toString() { return this.data; } }5.高级 其余部分涵盖了公有字段的高级优点。 6. 通过构造函数创建属性和通过字段创建属性的一个重要区别是:前者使用赋值,后者使用定义。这两者分别意味着什么?

6.1. 赋值属性

让我们首先来看看如何借助对象原型链来赋值属性。这个操作赋值操作符触发(=)。在下面的例子里,我们为.prop赋值(行A):

代码语言:javascript
复制
const proto = {
  set prop(value) {
    console.log('SETTER: '+value);
  }  
}
const obj = {
  __proto__: proto
}
obj.prop = 123; // (A)
assert.equal(obj.prop, undefined);

// Output:
// 'SETTER: 123'

在类中,通过赋值创建一个属性还会调用一个setter(如果有的话)。在下面的例子里,我们同样为.prop赋值(行A):

代码语言:javascript
复制
class A {
  set prop(value) {
    console.log('SETTER: '+value);
  }
}
class B extends A {
  constructor() {
    super();
    this.prop = 123; // (A)
  }
}
assert.equal(new B().prop, undefined);

// Output:
// 'SETTER: 123'

6.2. 定义属性

我们再次用原型链的例子开始说明定义一个属性是如何工作的。因为没有定义用的操作符,我们需要借助Object.defineProperty()来实现:

代码语言:javascript
复制
const proto = {
  set prop(value) {
    console.log('SETTER: '+value);
  }  
}
const obj = {
  __proto__: proto
}
Object.defineProperty(obj, 'prop', {value: 123});
assert.equal(obj.prop, 123);

最后调用的.defineProperty()属性描述,可以为一个对象指定属性(字符串)。它的值是字符形式,同时包含其他特性如writable——定义属性的值是否可变。

下面是一个使用定义而不是赋值创建的属性:

代码语言:javascript
复制
class A {
  set prop(value) {
    console.log('SETTER: '+value);
  }
}
class B extends A {
  prop = 123;
}
assert.equal(new B().prop, 123);

也就是说,公有字段总是创建属性并忽略setter。

6.3. 公有字段使用定义的利与弊

这是一些反对使用公有字段定义的争议点

  • 如果我们将一个属性的创建移出构造函数到一个字段,那么现有代码的行为就会改变。 这是一种重构风险。
  • 到目前为止,使用赋值运算符=,属性始终触发赋值。

这些是赞成使用定义的论据:

  • 在类的顶层声明的实体的理想模型是重写:始终创建实体,而不依赖于继承的实体。
  • 通过定义创建属性的先例包括:对象文字中的属性定义和类中的原型声明。

通常情况下,使用定义(而不是赋值)取决于对利弊的权衡。

  1. 公有实例字段何时被执行? 公有实例字段被执行的时机粗略地遵循这两条规则:class SuperClass { superProp = console.log('superProp'); constructor() { console.log('super-constructor'); } } class SubClass extends SuperClass { subProp = console.log('subProp'); constructor() { console.log('Before super()'); super(); console.log('sub-constructor'); } } new SubClass(); // Output: // 'Before super()' // 'superProp' // 'super-constructor' // 'subProp' // 'sub-constructor'class MyClass { prop = this; } const instance = new MyClass(); assert.equal(instance.prop, instance);
  2. 在基类中,公有实例字段在构造函数之前被立即执行
  3. 在子类中,公有实例字段在super()方法后被立即执行 看起来就像这样:
  4. 字段初始化时的作用域 在公有实例字段的初始化中,这指的是当前实例:

在公有静态字段的初始化中,这指的是当前类:

代码语言:javascript
复制
class MyClass {
  static prop = this;
}
assert.equal(MyClass.prop, MyClass);

此外,超类按预期工作:

代码语言:javascript
复制
class SuperClass {
  getValue() {
    return 123;
  }
}
class SubClass extends SuperClass {
  prop = super.getValue();
}
assert.equal(new SubClass().prop, 123);
  1. 公有字段的属性 默认地,公有字段是可写的,可枚举的和可配置的:class MyClass { static publicStaticField; publicInstanceField; } assert.deepEqual( Object.getOwnPropertyDescriptor( MyClass, 'publicStaticField'), { value: undefined, writable: true, enumerable: true, configurable: true }); assert.deepEqual( Object.getOwnPropertyDescriptor( new MyClass(), 'publicInstanceField'), { value: undefined, writable: true, enumerable: true, configurable: true });

更多关于属性的值、可写、可枚举、可配置的信息,参见"JavaScript for impatient programmers".

  1. 可用性 类字段当前可以应用在:
  2. Babel 7.0+。在这个特性进入到stage 4之前,都必须通过插件@babel/plugin-proposal-class-properties来启用。
  3. Node.js 12+
  4. Chrome: 公有字段:Chrome 72+ 私有字段: Chrome 74+

更多信息,参见对应提案

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档