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()/這個值。

相關文章
相關標籤/搜索