JavaScript的类字段声明(提案)

1.类的自定义属性

要定义在单击时递增的计数器窗口组件,可使用ES2015定义如下内容:程序员

class Counter extends HTMLElement {
  clicked() {
    this.x++;
    window.requestAnimationFrame(this.render.bind(this));
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
    this.x = 0;
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.x.toString();
  }
}
window.customElements.define('num-counter', Counter);
复制代码

2. 预先声明属性

使用ESnext字段声明提议,上面的示例能够写成:编程

class Counter extends HTMLElement {
  x = 0;

  clicked() {
    this.x++;
    window.requestAnimationFrame(this.render.bind(this));
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.x.toString();
  }
}
window.customElements.define('num-counter', Counter);
复制代码

在上面的示例中,您能够看到使用语法x = 0声明的字段。您还能够将没有初始值设定项的字段声明为x。bash

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

私有属性

下面的示例有一些实现细节,属性能够更好地保留在内部。 使用ESnext私有字段和方法,能够将定义细化为:闭包

class Counter extends HTMLElement {
  #x = 0;

  clicked() {
    this.#x++;
    window.requestAnimationFrame(this.render.bind(this));
  }

  constructor() {
    super();
    this.onclick = this.clicked.bind(this);
  }

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.#x.toString();
  }
}
window.customElements.define('num-counter', Counter);
复制代码

要使字段私有,只需给它们一个以#开头的名称。异步

经过定义在类外部不可见的内容,ESnext提供了更强大的封装,确保您的类的用户不会由于依赖于内部而产生意外。编程语言

请注意,ESnext仅在字段声明中提供预先声明的私有字段,正常属性的方式不能建立。函数

主要设计要点

使用Object.defineProperty建立的公有属性

公有属性声明使用Object.defineProperty(咱们在TC39术语中将[[Define]]语义引用)定义实例上的字段,而不是使用this.field = value; (称为[[Set]]语义)。 如下是影响的示例:工具

class A {
  set x(value) { console.log(value); }
}
class B extends A {
  x = 1;
}
复制代码

使用所采用的语义,new B()将致使一个对象具备值为1的属性x,而且不会将任何内容写入控制台。 使用备用[[Set]]语义,1将被写入控制台,而且尝试访问该属性将致使TypeError(由于缺乏getter)。优化

在[[Set]]和[[Define]]之间进行选择是一种设计决策,它对比了不一样类型的行为预期:预期字段将做为数据属性建立,而无论超类包含什么;预期setter将被调用。通过长时间的讨论,TC39肯定了[[Define]]语义,发现保持第一个指望很重要。ui

基于Object.defineProperty的公共字段语义的决定是基于TC39内部的普遍讨论和与开发人员社区的协商。不幸的是,社区至关分裂,而TC39则强烈支持Object.defineProperty。

做为一种缓解,decorators建议提供了编写decorator的工具,使公共字段声明使用[[Set]]语义。即便您不一样意默认值,也可使用其余选项。(不管TC39选择哪一种缺省值,都是这种状况。)

公共字段在Chrome 72中带有[[Define]]语义,这个语义决定不太可能被从新访问。

没有初始化项的字段被设置为undefined

不管是否存在初始化,公共字段声明和私有字段声明都会在实例中建立一个字段。若是没有初始化器,则将字段设置为undefined。这与某些转置器实现稍有不一样,后者将彻底忽略没有初始化器的字段声明。

例如,在下面的示例中,new D将生成一个对象,其y属性未定义,而不是1。

class C {
  y = 1;
}
class D extends C {
  y;
}
复制代码

将没有初始化项的字段设置为undefined(而不是擦除它们)的语义是,字段声明提供了可靠的基础,以确保在建立的对象上呈现属性。这有助于程序员将对象保持在相同的通常状态,这能够很容易地进行推理,有时在实现中更易于优化。

私有语法

私有字段基于使用#的语法,在声明字段和访问字段时都使用#。

class X {
  #foo;
  method() {
    console.log(this.#foo)
  }
}
复制代码

这种语法试图既简洁又直观,尽管它与其余编程语言有很大的不一样。 没有私有的计算属性名:#foo是一个私有标识符,#[foo]是一个语法错误。

没有后门访问私有属性

私有字段提供了一个强大的封装边界:从类外部访问私有字段是不可能的,除非有一些显式代码来公开它(例如,提供getter)。这与JavaScript属性不一样,JavaScript属性支持各类反射和元编程,而相似于闭包和WeakMap等机制,这些机制不提供对其内部的访问。

执行初始化

在构造函数运行时,公共字段和私有字段都按声明的顺序添加到实例中。初始化器将为每一个类实例从新计算。字段在初始化器运行后当即添加到实例中,而后再计算下面的初始化器。

做用域:做为初始化器表达式中的这个值,正在构造的实例位于做用域中。新的。目标未定义,如在方法中。对参数的引用是早期的错误。超方法调用Super .method()在初始化器中可用,可是超构造函数调用Super()是一个语法错误。即便类在异步函数/genenerator中声明,初始化器中也不能使用wait和yield。

当计算字段初始化器并将字段添加到实例时:

  • 基类:在构造函数执行的开始,甚至在参数析构以前。
  • 派生类:就在super()返回以后。(调用super()的灵活性致使许多实现为这种状况建立了一个单独的不可见initialize()方法。)

若是派生类中没有调用super(),而且没有将其余一些公共和私有字段添加到实例中,也没有计算初始化器。对于基类,初始化器老是被求值,即便构造函数最终返回其余东西。new.initialize proposal将添加一种方法,以编程方式向一个实例添加字段,而该实例不是来自基类中的super()/这个值。

相关文章
相关标签/搜索