ECMA 2022 (es13) 新特性

guojikun發表於2021-10-12

本文主要整理了截至到 2021年10月12日 為止的且處於 Stage 3->Stage 4 階段的ECMA提案。

主要包括:

  • Class Fields
  • RegExp Match Indices
  • Top-Level await
  • Ergonomic brand checks for Private Fields
  • .at
  • Object.hasOwn()
  • Class Static Block

Class Fields

class 相關的提案一共有三個,目前都處於 Stage 3 階段。

Private instance methods and accessors

示例:

/**** es2015 ****/
class Counter extends HTMLElement {
  get x() { return this.xValue; }
  set x(value) {
    this.xValue = value;
    window.requestAnimationFrame(this.render.bind(this));
  }

  clicked() {
    this.x++;
  }

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

  connectedCallback() { this.render(); }

  render() {
    this.textContent = this.x.toString();
  }
}
window.customElements.define('num-counter', Counter);
/**** now(exNext) ****/
class Counter extends HTMLElement {
  xValue = 0; // 宣告共有屬性

  get x() { return this.xValue; }
  set x(value) {
    this.xValue = value;
    window.requestAnimationFrame(this.render.bind(this));
  }

  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);

Class Public Instance Fields & Private Instance Fields

示例:

// 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);
// now 
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);
// 結合私有欄位
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);

Static class fields and private static methods

class Test {

    static instance = new Test(0);// 靜態共有變數

    static #value=123; // 靜態私有變數
    // 靜態私有方法
    static #print(){
        return this.#value;
    }
}

RegExp Match Indices

正則的匹配模式之前只有i g m,現在增加 d,目前處於 Stage 4 階段。

示例:

const re1 = /a+(?<Z>z)?/d;

// indices are relative to start of the input string:
const s1 = "xaaaz";
const m1 = re1.exec(s1);
m1.indices[0][0] === 1;
m1.indices[0][1] === 5;
s1.slice(...m1.indices[0]) === "aaaz";

m1.indices[1][0] === 4;
m1.indices[1][1] === 5;
s1.slice(...m1.indices[1]) === "z";

m1.indices.groups["Z"][0] === 4;
m1.indices.groups["Z"][1] === 5;
s1.slice(...m1.indices.groups["Z"]) === "z";

// capture groups that are not matched return `undefined`:
const m2 = re1.exec("xaaay");
m2.indices[1] === undefined;
m2.indices.groups["Z"] === undefined;

使用 \d 後給 上面程式碼中的 m1 增加 indices 屬性。

Top-Level await

之前只能 async await, 現在則可以直接使用 await ,目前處於 Stage 4 階段。

示例:

/*** before ***/
// awaiting.mjs
import { process } from "./some-module.mjs";
export default (async () => {
  const dynamic = await import(computedModuleSpecifier);
  const data = await fetch(url);
  const output = process(dynamic.default, data);
  return { output };
})();
// usage.mjs
import promise from "./awaiting.mjs";

export default promise.then(({output}) => {
  function outputPlusValue(value) { return output + value }

  console.log(outputPlusValue(100));
  setTimeout(() => console.log(outputPlusValue(100), 1000);

  return { outputPlusValue };
});

/*** now ***/
// awaiting.mjs
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
// usage.mjs
import { output } from "./awaiting.mjs";
export function outputPlusValue(value) { return output + value }

console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);

Ergonomic brand checks for Private Fields

這個提案的作用是檢測一個物件中是否存在私有變數,目前處於 Stage 4 階段。

示例:

class C {
  #brand;

  #method() {}

  get #getter() {}

  static isC(obj) {
    return #brand in obj && #method in obj && #getter in obj;
  }
}

.at

在所有基本可索引類中新增 .at() 方法,目前處於 Stage 3 階段。

很多時候,類似於 Python 中的陣列負值索引可以非常實用。比如在 Python 中我們可以透過 arr[-1] 來訪問陣列中的最後一個元素,而不用透過目前 JavaScript 中的方式來訪問 arr[arr.length-1]。這裡的負數是作為從起始元素(即arr[0])開始的反向索引。

但是現在 JavaScript 中的問題是,[] 這個語法不僅僅只是在陣列中使用(當然在 Python 中也不是),而在陣列中也不僅僅只可以作為索引使用。像arr[1]一樣透過索引引用一個值,事實上引用的是這個物件的 "1" 這個屬性。所以 arr[-1] 已經完全可以在現在的 JavaScript 引擎中使用,只是它可能不是代表的我們想要表達的意思而已:它引用的是目標物件的 "-1" 這個屬性,而不是一個反向索引。

這個提案提供了一個通用的方案,我們可以透過任意可索引的型別(Array,String,和 TypedArray)上的 .at 方法,來訪問任意一個反向索引、或者是正向索引的元素。

示例:

const arr = [1,2];
arr.at(-1); // 2
arr.at(-2); // 1

Object.hasOwn()

採用一種使更易於訪問的方法替代 Object.prototype.hasOwnProperty(),目前處於 Stage 4 階段。

其實現在我們就可以透過 Object.prototype.hasOwnProperty 來使用提案所包含的特性。但是直接透過物件自身的 hasOwnProperty 來使用 obj.hasOwnProperty('foo') 是不安全的,因為這個 obj 可能覆蓋了 hasOwnProperty 的定義,MDN 上也對這種使用方式進行了警告。

JavaScript 並沒有保護 hasOwnProperty 這個屬性名,因此,當某個物件可能自有一個佔用該屬性名的屬性時,就需要使用外部的 hasOwnProperty 獲得正確的結果...

示例:

let hasOwnProperty = Object.prototype.hasOwnProperty

if (hasOwnProperty.call(object, "foo")) {
  console.log("has property foo")
}

// 此提案將該程式碼簡化為:

if (Object.hasOwn(object, "foo")) {
  console.log("has property foo")
}

Class Static Block

類塊提供了在類定義評估期間執行額外靜態初始化的機制,目前處於 Stage 4 階段

自從有了 Class Private Fields,對於類的語法是不斷地有新的實踐與需求。這個提案提議的 Class Static 初始化塊會在類被執行、初始化時被執行。

提案中定義的初始化程式碼塊可以獲得 class 內的作用域,如同 class 的方法一樣,也意味著可以訪問類的 #欄位

let getX;

export class C {
  #x
  constructor(x) {
    this.#x = { data: x };
  }

  static {
    // getX has privileged access to #x
    getX = (obj) => obj.#x;
  }
}

export function readXData(obj) {
  return getX(obj).data;
}

參考資料

相關文章