tc39 proposal: Classes static fields and methods

zhangbao發表於2020-10-02

提案地址:github.com/tc39/proposal-static-class-features

回顧

ES2015 引入了 static 關鍵字,在類中使用,用來建立 static public method(and getter/setters),案例如下

class Foo {
  static bar() {}
  static set baz(val) {}
  static get baz() {}
}

這種方式相當於在類(即這裡的 SkinnedMesh)上直接宣告屬性,而非在類例項上。

新語法

當然,只有一個 Static public methods 的功能顯然是不夠的,於是基於 class field declarations / class private methods and getter/setters 提案中的語法,該提案(處於 Stage 3 階段)做了如下的語法補充:

  • Static public fields
  • Static private fields
  • Static private methods(and getter/setters)

Static public fields

class Foo  {
    // public static method(ES2015)
    static baz() {}
    // public static field(Stage 3)
    static bar = function () {}
}

看到上面的語法時,我有點疑惑,public static method/field 的區別在哪裡?我在控制檯上列印了下兩個屬性的描述符情況:

tc39 proposal: Classes static fields and methods

根據列印結果可以推測,它們的區別僅在與該靜態屬性是否可被遍歷。public static field 是可遍歷的(enumerable: true),ES2015 中引入的 public static method 則是不可遍歷的(enumerable: false)。

這份程式碼,使用 Babel 轉譯成 ES5 程式碼,如下:

var Foo = /*#__PURE__*/ (function () {
  function Foo() {
    _classCallCheck(this, Foo);
  }

  // `baz` (public static method)轉換後的程式碼
  _createClass(Foo, null, [
    {
      key: "baz",
      value: function baz() {}
    }
  ]);

  return Foo;
})();

// `bar` (public static field)轉換後的程式碼
_defineProperty(Foo, "bar", function () {});

再分別來看看 _createClass_defineProperty 函式的定義:

function _defineProperties(target, props) {
  for (var i = 0; i < props.length; i++) {
    var descriptor = props[i];
    // descriptor: { key: "baz", value: function baz() {} }
    // (2) descriptor 中不含包 enumerable,最終 descriptor 的 enumerable 屬性值為 false
    descriptor.enumerable = descriptor.enumerable || false;
    descriptor.configurable = true;
    // (3) 如果是,資料屬性,設定成可寫的(writable: true),
    // (4) 訪問器屬性原樣輸出,不做額外處理
    if ("value" in descriptor) descriptor.writable = true;
    Object.defineProperty(target, descriptor.key, descriptor);
  }
}

function _createClass(Constructor, protoProps, staticProps) {
  if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  // (1) 靜態方法 `baz` 的呼叫方式:
  // _createClass([ { key: "baz", value: function baz() {} } ])
  if (staticProps) _defineProperties(Constructor, staticProps);
  return Constructor;
}

// 靜態成員 `bar` 的呼叫方式:
// _defineProperty(Foo, "bar", function () {});
function _defineProperty(obj, key, value) {
  // 已有該屬性,則以 [[Define]] 的方式定義
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  // 否則以 [[Set]] 的方式定義
  } else {
    obj[key] = value;
  }
  return obj;
}

結論是,兩者其實本質上區別不大,僅有的一點:一個是可遍歷的,一個是不可遍歷的。

Static private fields

class ColorFinder {
  static #red = "#ff0000";
  static #green = "#00ff00";
  static #blue = "#0000ff";

  static colorName(name) {
    switch (name) {
      case "red": return ColorFinder.#red;
      case "blue": return ColorFinder.#blue;
      case "green": return ColorFinder.#green;
      default: throw new RangeError("unknown color");
    }
  }

  // Somehow use colorName
}

這塊很好理解,靜態方法內部使用了私有成員,這些成員是直接定義在 ColorFinder 這個類上的,而不是例項上。這樣就有效避免了在每個例項上單獨建立同一份私有變數帶來的記憶體消耗;另外,也比單獨在外面另宣告一個常量,邏輯上更加緊湊。

Static private methods(and getter/setters)

export const registry = new JSDOMRegistry();

export class JSDOM {
  #createdBy;

  #registerWithRegistry() {
    // ... elided ...
  }

  static async fromURL(url, options = {}) {
    normalizeFromURLOptions(options);
    normalizeOptions(options);

    const body = await getBodyFromURL(url);
    return JSDOM.#finalizeFactoryCreated(new JSDOM(body, options), "fromURL");
  }

  static async fromFile(filename, options = {}) {
    normalizeOptions(options);

    const body = await getBodyFromFilename(filename);
    return JSDOM.#finalizeFactoryCreated(new JSDOM(body, options), "fromFile");
  }

  static #finalizeFactoryCreated(jsdom, factoryName) {
    jsdom.#createdBy = factoryName;
    jsdom.#registerWithRegistry(registry);
    return jsdom;
  }
}

上面的程式碼,在靜態公共方法中使用靜態私有方法建立 JSDOM 例項,隱藏了內部的實現細節。

總結

在我看來,本提案中提到的語法補充,在實際開發過程中,並不很常用。除了 Static public fields 我覺得會稍微用的多些,但是這部分功能,也幾乎能用 static public method 方式替換。而 Static private fields/methods 的使用,應該都是在不斷最佳化中程式碼中,開始注意去使用的。

(完)

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章