圍觀!我國第一個推進到瀏覽器標準的 ECMaScript 提案!

語言: CN / TW / HK

在2022年6月22日,第123屆Ecma大會批准了 ECMAScript 2022 語言規範。一起來看看ES13給我們帶來了哪些新特性吧。其中最後一個特性,是我國第一個推進到瀏覽器標準的EcmaScript提案。

1.私有屬性和私有方法

之前js中 class 的一個痛點就是沒有私有屬性,畢竟封裝是面向物件的三大特性,沒有私有屬性如何實現封裝。儘管我們可以用很多方法去曲線實現私有屬性比如閉包 proxyweakmapsymbol 等等,但是他們各自有各自的缺陷,下面的程式碼演示其中以 _ 作為命名約定的方式實現私有屬性的缺點。

class User {
constructor() {
// public 屬性
this.name = "Tom";
// private 屬性
this._lastName = "Brown";
}

getFullName(){
return `${this.name} ${this._lastName}`
}
}

const user = new User();
user.name
// "Tom"

user._lastName
// "Brown"
// no error thrown, we can access it from outside the class

在建構函式中,我們定義了兩個欄位,其中一個欄位是以_開頭的, 這是一個命名約定用於將欄位宣告為私有欄位,但是當我們在類外面訪問該欄位時,並沒有引起報錯,原因是因為它只是一個命名規範而已。

現在在 ES13 中,我們可以用更簡單的方法宣告公共和私有欄位,第一,我們不必在建構函式中定義它們了,其次,我們可以通過在欄位前面新增#號來定義一個真正的私有欄位。

class User {
name = "Tom";
#lastName = "Brown"

getFullName(){
return `${this.name} ${this.#lastName}`
}
}

const user = new User();
user.name
// "Tom"

user.getFullName();
// "Tom Brown"

user.#lastName
// SyntaxError - cannot be accessed or modified from outside the class

有了這個特性,我們終於可以簡單輕鬆的定義一個真正的私有屬性或私有方法了。

2.私有屬性檢測

當我們嘗試訪問物件上不存在的私有屬性時會報錯,所以就需要一種方法來檢測物件是否具有某私有屬性,此時,我們可以在 class 中使用 in 關鍵字來完成此工作。

class Person {
#name;
constructor(name) {
this.#name = name;
}
static check(obj) {
return #name in obj;
}
}

Person.check(new Person()), // true

當然,你可能會發問對於具有相同名稱的私有屬性的類, in 關鍵字是否能正常工作,別擔心,請參考以下例子,User類和Person類具有相同的私有屬性name, in 關鍵字可以正確的區分。

class User {
#name;
constructor(name) {
this.#name = name;
}
static check(obj) {
return #name in obj;
}
}

class Person {
#name;
constructor(name) {
this.#name = name;
}
static check(obj) {
return #name in obj;
}
}

User.check(new User()), // true
User.check(new Person()), // false
Person.check(new Person()), // true
Person.check(new User()), // false

3. 類靜態初始化塊(Class Static Block)

類在初始化的時候會執行靜態初始化塊( Class Static Block ),一個類可以擁有任意個靜態初始化塊,它們將會按照宣告的順序執行。靜態初始塊內還可以訪問父類的靜態屬性。

class Dictionary {
static words = [ "yes", "no", "maybe" ];
}


class Words extends Dictionary {
static englishWords = [];
static #localWord = 'ok';
// 第一個靜態初始化塊
static {

const words = super.words;
this.englishWords.push(...words);
}
// 第二個靜態初始化塊
static {
this.englishWords.push(this.#localWord);
}
}

console.log(Words.englishWords)
//輸出 -> ["yes", "no", "maybe", "ok"]

下面這個例子,我們還可以通過靜態初始化塊將私有屬性暴露出去。

let getClassPrivateField;

class Person {
#privateField;
constructor(value) {
this.#privateField = value;
}
static {
getClassPrivateField = (obj) => obj.#privateField;
}
}

getClassPrivateField(new Person('private value'));

4.Regexp Match Indices

之前 ECMAScript 中的 「RegExp.prototype.exec」 方法的返回值已經提供了對於匹配的捕獲組 文字與對應的捕獲組在正則表示式中的索引。儘管它已經包含輸入字串,模式匹配的索引以及包含任何已命名捕獲組的子字串的group物件,但是對於複雜情況,此資訊可能不足。

const fruits = 'Fruits: apple, banana, orange'
const regex = /(banana)/g;

const matchObj = regex.exec(fruits);

console.log(matchObj);
// [
// 'banana',
// 'banana',
// index: 15,
// input: 'Fruits: apple, banana, orange',
// groups: undefined
// ]

所以ES13通過新增/d修飾符,向匹配結果新增一個屬性 .indices ,此屬性是一個索引陣列,其中包含每個捕獲的子字串的一對開始索引和結束索引。

const fruits = 'Fruits: apple, banana, orange'
const regex = /(banana)/gd;

const matchObj = regex.exec(fruits);

console.log(matchObj);
// [
// 'banana',
// 'banana',
// index: 15,
// indices:[
// [15, 21],
// [15, 21]
// ]
// input: 'Fruits: apple, banana, orange',
// groups: undefined
// ]

5.Await operator at the top-level

之前 await 關鍵詞只能在 aysnc function 裡進行使用,想要在頂層使用 await 就必須要加個 aysnc 自執行函式,這樣十分的不方便, 所以ES13中,引入了可以直接在頂層使用 Await 關鍵字的特性。

動態載入模組:

const strings = await import(`./example.mjs`); |

依賴回退:

let jQuery;
try {
  jQuery = await import('https://cdn-a.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.com/jQuery');
}

載入最快的資源

const resource = await Promise.any([
fetch('http://example1.com'),
fetch('http://example2.com'),
]);

6 .at()方法

之前,我們如果想要獲取一個數組的元素,如果是正序獲取一個元素,我們可以採取 arr[0] 這樣的方式, 但是如果我們要倒序訪問一個數組的元素,就需要採取 arr[arr.length - 2] 這樣的方式,陣列的名稱 arr 我們寫了兩次,而且還要寫個 length ,這很不優雅,複雜的情況下就更是難受。

const arr = [100,200,300,400]
arr[0] // 100
arr[arr.length - 2] // 300
arr.slice(-2)[0] // 300

為了解決這個問題, ES13 引入了 at() 方法,無論正序還是倒序獲取元素都非常的優雅,有了這個方法,我們可以在陣列、字串、 TypedArray 上通過索引值方便的獲取元素。

const arr = [100,200,300,400]
arr.at(0) // 100
arr.at(-2) // 300

const str = "ABCD"
str.at(-1) // 'D'
str.at(0) // 'A'

7.Object.hasOwn(obj, propKey)

之前我們如果想要判斷某物件是否具有某屬性,我們會通過 inobj.hasOwnProperty ,但是如果指定的屬性位於原型鏈中,“in”運算子也會返回 true 。所以之前要想判斷物件自身是否具有某屬性我們一般都通過 obj.hasOwnProperty 來判斷。

但是 obj.hasOwnProperty 也有自身的缺點:因為 hasOwnProperty 是不受保護的屬性,所以,人們可能會在物件上定義個自己的 hasOwnProperty ,如下所示,自定義的 hasOwnProperty 永遠返回 false ,這是一個坑。

const person = {
name: "Roman",
hasOwnProperty:()=> {
return false
}
}

person.hasOwnProperty('name'); // false

另一個問題是使用 Object.create(null) 建立一個不繼承自  Object.prototype 的物件,使  hasOwnProperty 方法時會報錯。

Object.create(null).hasOwnProperty("name")
// Uncaught TypeError: Object.create(...).hasOwnProperty is not a function

所以,為了解決上面的問題,ES13引入了**Object.hasOwn(obj, propKey)**。用法如下所示:

const object = { name: "Mark" };
Object.hasOwn(object, "name"); // true

const object2 = Object.create({ name: "Roman" });
Object.hasOwn(object2, "name"); // false
Object.hasOwn(object2.__proto__, "name"); // true

const object3 = Object.create(null);
Object.hasOwn(object3, "name"); // false

8.Error Cause

Error Cause 提案由阿里巴巴的 「昭朗」 同學負責,是我國第一個成為瀏覽器標準的 EcmaScript 提案。

以前因為 Error Cause 沒有標準化的引數定義及官方實現,所以容易丟失 error 的屬性或需要寫比較多的程式碼自定義等,並且開發者工具也難以依賴於非語言特性的自定義方案。

  try {
apiCallThatCanThrow();
} catch (err) {
throw new Error('New error message', { cause: err });
}

有了這個新特性,藉助 cause 屬性,我們可以記下導致這個 Error 的前一個 Error 物件,錯誤物件就可以以一種簡單優雅的方式連結起來。

9.總結

以上就是ES13的新特性,新特性會給js生態帶來更多好的東西,提高開發者的效率和體驗。