JSDC 2021 前導活動 | ECMAScript 關鍵 30 天 | Are you ready? ES2022!

JSDC 2021 前導活動 | ECMAScript 關鍵 30 天 | Are you ready? ES2022!

前言

ECMAScript 原本是不定期地釋出版本,但因應提案的踴躍和開發需求的迫切,所以從 ES2015 後就改為一年一修。也就是說每年都會有新的語法標準出現,讓開發者可以使用更簡潔彈性的語法撰寫,或是實現更強大的功能。

接下來整理預計會在 ES2022 釋出的提案,以簡單的說明加上清楚的程式範例,快速了解起手式💪

ECMAScript 提案五階段

一個想法出來到納入提案,接著成為修訂標準,通常會經歷以下 5 個階段-

Stage 說明
0 由 TC-39 的成員或其他人提出後,委員會沒有否決的討論或想法
1 正式成為提案,需有具體語法和描述,部分會有Polyfill的實現
2 可能有相關的運作環境或編譯器提供實驗性的功能實現
3 成為候選提案,部分運作環境或編譯器提供原生支援
4 通過至少兩個驗收測試,等待下版釋出時成為修訂內容

今天要講的語法,都是已經進到 finished,也就是 Stage 4 階段的提案。有興趣的話,可以前往 TC-39 的 GitHub 看相關整理。有趣的是,在這裡不僅可以看到語法的開發動機和具體描述,也能看到在提案推進的過程中,開發者與委員們的討論紀錄,可以更進一步地了解標準推出的始末。

正規表達式

先簡單複習一下。正規表達式主要有以下三種組成-

  • 以指定的文字加上特殊字元組成匹配的句法。
  • 由兩個雙斜線(/)包覆匹配的句法。
  • 後面可以選擇性地加上一個或多個旗標,設定全域的查找規則。

其中旗標的部分,一個 RegExp 物件會針對旗標提供實體屬性來查詢。透過取得屬性值,就可以知道這個正規表達式有沒有設定對應的旗標。

Stage 對應屬性 說明
d hasIndices 回傳每個匹配內容的起始&下次要開始檢索的索引陣列
([startIndex, endIndex+1])

有加上 d 旗標的正規表達式,在執行匹配相關的操作,例如 exec 時,可以多回傳一個 indices 屬性。這個屬性會包含匹配內容的起始索引,以及結束索引加 1,也就是視為下次要開始檢索的索引。

目前最新版本的 Chrome 還有Node.js中可以運作,有興趣的話可以嘗試看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const myRegexp = /\w*.o\w+/dgi; // 找出中間有 o 的單字
const target = 'Born to make history';

let currResult;
while ((currResult = myRegexp.exec(target)) !== null) {
console.log('這次的符合內容: ', currResult[0]);
console.log('Matched Indices: ', currResult.indices[0]);
console.log('---');
}

// 這次的符合內容: Born
// Matched Indices: (2) [0, 4]
// ---
// 這次的符合內容: history
// Matched Indices: (2) [13, 20]
// ---

具有索引的標準內建物件

常見的這類物件有字串跟陣列。那麼在 ES2022 推出了什麼相關語法呢?

indexable.at(index)

用途跟使用字面值取得元素的方式類似,透過索引的傳入來獲得元素。不過索引值在負整數跟浮點數時,跟字面值的回傳結果會不一樣。可以看一嚇得比較表-

取得元素的方式 負整數 浮點數
indexable[index] undefined undefined
indexable.at(index) 後面從 -1 開始算 無條件捨去後,再由正負數決定查找方向

目前最新版本的 Chrome 可以運作,有興趣的話可以嘗試看看。

1
2
3
4
const myArray = [0, 1, 2, 3];
console.log(myArray[-2], myArray.at(-2)); // undefined 2
console.log(myArray[1.6], myArray.at(1.6)); // undefined 1
console.log(myArray[-3.2], myArray.at(-3.2)); // undefined 1

物件

Object.hasOwn(target, propName)

用來檢查目標物件有沒有屬性。那跟目前習慣使用的 hasOwnProperty 有什麼不一樣呢?

假設以 Object.create(null) 建立了空物件,然後新增一些屬性後,我們想檢查這個物件有沒有特定屬性。可是物件的原型指向了 null,所以就無法使用實體方法 hasOwnProperty,除非要直接呼叫 Object.prototype.hasOwnProperty 加上 call 方法來達到這個目的。不過這樣的寫法太冗長了。因此 ES2022 就在 Object 底下新增這個靜態方法,成為語法糖。

目前最新版本的 Chrome 可以運作,有興趣的話可以嘗試看看。

1
2
3
4
5
// before
const hasNameProp = Object.prototype.hasOwnProperty.call(myObject, 'name');

// after
const hasNameProp = Object.hasOwn(myObject, 'name');

類別

類別的相關語法在 ES2022 中占了蠻大一部分的內容。其中可以把這些修訂分類為-

  • 擴充靜態關鍵字(static)的使用
  • 正式支援私有(private)的機制

靜態方法與成員(Static methods and fields)

在 ES2015 時提出的 static 關鍵字,只能做為公開靜態方法的前綴字。不過在 ES2022 後,static 關鍵字的應用範圍更廣泛了。無論是屬性或方法,公開或私有,只要在名稱前加上前綴字,就能表示為靜態。

目前最新版本的 Chrome 可以運作,有興趣的話可以嘗試看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Inbody {
static #secretNumber = 1.2; // ES2022: 私有靜態成員
static brand = 'My Inbody'; // ES2022: 公開靜態成員

// ES2022: 私有靜態方法
static #getPBF(weight, fat) {
return ((fat * Person.#secretNumber) / weight) * 100;
}

// ES2015: 公開靜態方法
static getBMI(weight, height) {
return weight / Math.pow(height / 100, 2);
}
}
Inbody.country = 'Taiwan'; // ES2015: 公開靜態成員

靜態初始化區塊(Static initialization blocks)

類別初始化時,有些靜態成員需要透過流程控制設定初始值的話,通常只能在類別建立後,把相關的初始流程放在之後。在程式的語意上會像是兩個獨立的區塊拼在一起,規模越複雜的話,就會降低維護性。

1
2
3
4
5
6
7
8
9
10
class APILibrary {
static configs;
}

try {
const fetchedConfigs = await fetchConfigs();
APILibrary.configs = { ...fetchedConfigs, tag: 1 };
} catch (error) {
APILibrary.configs = { root: 'myroot/api', tag: 2 };
}

ES2022後,類別中只要使用 static 關鍵字加上大括號({ }),就能包覆靜態成員的初始流程。這樣做還有個好處是,流程中需要存取類別的私有成員時,可以直接取用,而不用再撰寫額外存取器方法來封裝,寫法上算是優雅很多。

1
2
3
4
5
6
7
8
9
10
11
12
class APILibrary {
static configs;
static #defaultRoot = 'myroot/api';
static {
try {
const fetchedConfigs = await fetchConfigs();
APILibrary.configs = { ...fetchedConfigs, tag: 1 };
} catch (error) {
APILibrary.configs = { root: APILibrary.#defaultRoot, tag: 2 };
}
}
}

定義私有方法與成員(Private methods and fields)

成為私有成員,表示只有內部可以使用,外部如果嘗試存取或呼叫的話,就會回傳 undefined 或錯誤提示。這樣做可以保護成員不會被外部任意修改,或設定只有內部可以操作的方法等。

在 ES2022 後,正式把 # 作為私有成員的前綴字。像是上面的範例程式-static #defaultRoot = 'myroot/api';defaultRoot 屬性就是一種私有屬性。

需要注意的是,不只是宣告,在呼叫方法或存取屬性時也需要冠上這個前綴字。更多有關私有方法與成員的範例程式與說明,可以參考我即將上市的書-ECMAScript關鍵30天

頂層的 await

awaitasync 的語法是非同步處理的語法糖。只要函式中有 await 的關鍵字出現,就一定要在該函式名稱的前面加 async 的前綴字。在 ES2022 後,可以允許在需要非同步的地方加上 await 就好,不需使用async函式封裝,提升了撰寫非同步的彈性與簡潔性。

1
2
3
4
5
6
7
8
9
// case 1
import { fetchAppConfig } from '../APILibrary';
const appConfig = await fetchAppConfig();

// case 2
const menuData = fetch('api/config/menu.json').then((response) =>
response.json()
);
export default await menuData;

Babel

如果有在專案中使用 Babel,並且設定 @babel/preset-env 這個 preset 的話,就可以試試看目前有支援的轉換語法,像是頂層 await、類別的靜態初始化區塊等。

TypeScript

如果專案是以 TypeScript 開發,在目前的 beta 版可以透過 tsconfig.jsonmodule 設定來支援頂層 await。

小結

這篇文章的內容,主要是擷取自我即將上市的書。如果有興趣的話,也歡迎在 11/12 後去天瓏翻翻看,或是在博客來等網站翻一下試閱頁。有對到你的電波的話,歡迎把它帶回家喔😆

這篇內容也有影片版可以觀看。在這裡也謝謝 JSDC 的邀請,讓我有了直播技術分享的初體驗。另外 JSDC 當天的議程內容真的很充實,受益良多。有時間的話,會再陸續整理相關心得。

參考連結

JSDC 2021 前導活動 | ECMAScript 關鍵 30 天 | Are you ready? ES2022!

https://yuri-journal.me/軟體開發/2021110111/

作者

Yuri Tsai

發表於

2021-11-01

更新於

2022-06-20

許可協議

評論