ES13中11個令人驚歎的 JavaScript 新特性

發表於2023-09-19

前言

與許多其他程式語言一樣,JavaScript 也在不斷髮展。每年,該語言都會透過新功能變得更加強大,使開發人員能夠編寫更具表現力和簡潔的程式碼。 本葡萄今天就為大家介紹ES13中新增的最新功能,並檢視其用法示例以更好地理解它們。

1.類

在ES13之前,類欄位只能在建構函式中宣告。與許多其他語言不同,無法在類的最外層作用域中宣告或定義它們。

class Car {
    constructor() {
      this.color = 'blue';
      this.age = 2;
    }
  }
  const car = new Car();
  console.log(car.color); // blue
  console.log(car.age); // 

而ES13 消除了這個限制。現在我們可以編寫這樣的程式碼:

class Car {
  color = 'blue';
  age = 2;
}const car = new Car();
console.log(car.color); // blue
console.log(car.age); // 2

2.私有方法和欄位

ES13以前,不可能在類中宣告私有成員。成員傳統上帶有下劃線 ( \_) 字首,以表明它是私有的,但仍然可以從類外部訪問和修改它。

class Person {
  _firstName = 'Joseph';
  _lastName = 'Stevens';  get name() {
    return `${this._firstName} ${this._lastName}`;
  }
}const person = new Person();
console.log(person.name); // Joseph Stevens
// 仍可以從類外部訪問 // 原本打算設為私有的成員
console.log(person._firstName); // Joseph
console.log(person._lastName); // Stevens
// 也可以修改
person._firstName = 'Robert';
person._lastName = 'Becker';console.log(person.name); // Robert Becker

使用 ES13,我們現在可以透過在類前面新增 ( \#) 來向類新增私有欄位和成員。嘗試從外部訪問這些類將會引發錯誤:

class Person {
  #firstName = 'Joseph';
  #lastName = 'Stevens';  get name() {
    return `${this.#firstName} ${this.#lastName}`;
  }
}const person = new Person();
console.log(person.name);
// 語法錯誤:私有欄位 '#firstName' 必須在一個外層類中宣告
console.log(person.#firstName);
console.log(person.#lastName);

3.await頂層操作

在 JavaScript 中,await運算子用於暫停執行,直到 一個Promise被解決(執行或拒絕)。 以前只能在async中使用此運算子。不可以在全域性作用域中直接使用await。

function setTimeoutAsync(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}
//語法錯誤:await 僅在非同步函式中有效
await setTimeoutAsync(3000);

有了 ES13,現在我們可以:

function setTimeoutAsync(timeout) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, timeout);
  });
}
//  等待超時 - 沒有錯誤丟擲
await setTimeoutAsync(3000);

4.靜態類欄位和靜態私有方法

現在可以在 ES13 中為類宣告靜態欄位和靜態私有方法。靜態方法可以使用關鍵字this訪問類中的其他私有/公共靜態成員,例項方法可以使用this.constructor訪問他們。

class Person {
  static #count = 0;  static getCount() {
    return this.#count;
  }  constructor() {
    this.constructor.#incrementCount();
  }  static #incrementCount() {
    this.#count++;
  }
}const person1 = new Person();
const person2 = new Person();console.log(Person.getCount()); // 2

5.類靜態塊

ES13 引入了一項特性,允許開發者定義僅在建立類時執行一次的靜態塊。這一特性與其他物件導向程式語言(如 C\# 和 Java)中的靜態建構函式相似。

在一個類的主體中,你可以定義任意數量的靜態 {} 初始化塊。它們會按照宣告的順序與任何交錯的靜態欄位初始值設定項一起執行。此外,你還可以透過塊中的 super 關鍵字訪問超類的靜態屬性。這為開發者提供了更多的靈活性和控制能力。

class Vehicle {
  static defaultColor = 'blue';
}class Car extends Vehicle {
  static colors = [];  static {
    this.colors.push(super.defaultColor, 'red');
  }  static {
    this.colors.push('green');
  }
}console.log(Car.colors); // [ 'blue', 'red', 'green' ]

6.檢查物件中的私有欄位

開發者如今可以利用這一新功能,使用運算子in來方便地檢查物件是否包含某個特定的私有欄位。

class Car {
  #color;  hasColor() {
    return #color in this;
  }
}const car = new Car();
console.log(car.hasColor()); // true;

透過運算子in,可以準確區分不同類中具有相同名稱的私有欄位。

class Car {
  #color;  hasColor() {
    return #color in this;
  }
}class House {
  #color;  hasColor() {
    return #color in this;
  }
}const car = new Car();
const house = new House();console.log(car.hasColor()); // true;
console.log(car.hasColor.call(house)); // false
console.log(house.hasColor()); // true
console.log(house.hasColor.call(car)); // false

7.at() 索引方法

在 JavaScript 中,我們通常使用方括號[]來訪問陣列的第 t 個元素。這個過程非常簡單,但實際上我們只是訪問了索引為 t-1 的陣列屬性而已。

const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b

然而,當我們希望透過方括號來訪問陣列末尾的第 N 個元素時,我們需要使用索引 arr.length - N。

const arr = ['a', 'b', 'c', 'd'];
// 從末尾開始第一個元素
console.log(arr[arr.length - 1]); // d
// 倒數第二個元素 console.log 
console.log(arr[arr.length - 2]); // c

藉助全新的at()方法,可以以更加精簡和富有表現力的方式來實現這一目標。要訪問陣列末尾的第N個元素,只需將負值-N作為引數傳遞給at()方法即可。

const arr = ['a', 'b', 'c', 'd'];
// 從末尾開始第一個元素
console.log(arr.at(-1)); // d
// 倒數第二個元素 console.log 
console.log(arr.at(-2)); // c

除了陣列之外,字串和TypedArray物件現在也有at()方法。

const str = 'Coding Beauty';
console.log(str.at(-1)); // y
console.log(str.at(-2)); // tconst typedArray = new Uint8Array([16, 32, 48, 64]);
console.log(typedArray.at(-1)); // 64
console.log(typedArray.at(-2)); // 48

8.正規表示式匹配索引

在ES13之前,我們只能獲取字串中正規表示式匹配的起始索引,

const str = 'sun and moon';const regex = /and/;const matchObj = regex.exec(str);// [ 'and', index: 4, input: 'sun and moon', groups: undefined ]
console.log(matchObj);

使用ES13之後,可以透過指定一個/d正規表示式標誌來獲取匹配開始和結束的兩個索引。這一特性賦予了更多的靈活性和控制能力。

const str = 'sun and moon';
const regex = /and/d;
const matchObj = regex.exec(str);
/**
[
  'and',
  index: 4,
  input: 'sun and moon',
  groups: undefined,
  indices: [ [ 4, 7 ], groups: undefined ]
]
 */
console.log(matchObj);

設定標誌後d,返回的物件將具有indices包含起始索引和結束索引的屬性。

9.Object.hasOwn()方法

在 JavaScript 中,我們可以使用Object.prototype.hasOwnProperty()方法來檢查物件是否具有給定的屬性。

class Car {
  color = 'green';
  age = 2;
}const car = new Car();console.log(car.hasOwnProperty('age')); // true
console.log(car.hasOwnProperty('name')); // false

然而,這種方法存在一些問題。首先,Object.prototype.hasOwnProperty()方法並未受到保護,這意味著我們可以透過自定義的hasOwnProperty()方法來覆蓋它,而這個自定義方法可能會具有與Object.prototype.hasOwnProperty()不同的行為。需要額外注意的是這一點。

class Car {
  color = 'green';
  age = 2;  // This method does not tell us whether an object of
  // this class has a given property.
  hasOwnProperty() {
    return false;
  }
}const car = new Car();console.log(car.hasOwnProperty('age')); // false
console.log(car.hasOwnProperty('name')); // false

另外一個問題是,如果我們使用了 null 原型(透過 Object.create(null) 建立的物件),那麼試圖呼叫該方法將會產生錯誤。

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
// TypeError: obj.hasOwnProperty 不是函式
console.log(obj.hasOwnProperty('color'));

為了克服這些問題,我們可以利用屬性呼叫方法Object.prototype.hasOwnProperty.call()來解決。具體示例如下所示:

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;console.log(Object.prototype.hasOwnProperty.call(obj, 'color')); // true
console.log(Object.prototype.hasOwnProperty.call(obj, 'name')); // false

這種方式並不十分便利。為了避免重複,我們可以編寫一個可重用的函式,這樣可以使我們的程式碼更加簡潔和高效:

function objHasOwnProp(obj, propertyKey) {
  return Object.prototype.hasOwnProperty.call(obj, propertyKey);
}const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;console.log(objHasOwnProp(obj, 'color')); // true
console.log(objHasOwnProp(obj, 'name')); // false

現在不需要在那樣做了,我們還可以使用全新的內建方法Object.hasOwn()來處理這個問題。它與我們之前編寫的可重用函式類似,接受物件和屬性作為引數,並且返回一個布林值,如果指定的屬性是物件的直接屬性,則返回true;否則返回false。

const obj = Object.create(null);
obj.color = 'green';
obj.age = 2;
obj.hasOwnProperty = () => false;console.log(Object.hasOwn(obj, 'color')); // true
console.log(Object.hasOwn(obj, 'name')); // false

10.錯誤原因屬性

現在,錯誤物件已經增加了一個cause屬性,該屬性用於指定導致錯誤丟擲的原始錯誤。透過這種方式,我們可以為錯誤新增額外的上下文資訊,從而更好地診斷意外的行為。要指定錯誤的原因,我們可以在作為建構函式的第二個引數傳遞給Error()的物件中設定屬性來實現。這種方法能夠提供更豐富的錯誤追蹤和除錯資訊。

function userAction() {
  try {
    apiCallThatCanThrow();
  } catch (err) {
    throw new Error('New error message', { cause: err });
  }
}try {
  userAction();
} catch (err) {
  console.log(err);
  console.log(`Cause by: ${err.cause}`);
}

11.從陣列最後查詢

在 JavaScript 中,我們已經可以使用Array的find()方法來查詢陣列中滿足指定測試條件的元素。類似地,我們也可以使用findIndex()方法來獲取滿足條件的元素的索引值。儘管find()和findIndex()都是從陣列的第一個元素開始搜尋,但在某些情況下,從最後一個元素開始搜尋可能會更有效。

有些情況下,我們知道從陣列的末尾進行查詢可能會獲得更好的效能表現。例如,在這裡我們嘗試查詢陣列中prop屬性等於"value"的專案。這時候,可以透過使用reverse()方法將陣列反轉,然後使用find()和findIndex()方法來從末尾開始搜尋。下面是具體的實現示例:

const letters = [
  { value: 'v' },
  { value: 'w' },
  { value: 'x' },
  { value: 'y' },
  { value: 'z' },
];const found = letters.find((item) => item.value === 'y');
const foundIndex = letters.findIndex((item) => item.value === 'y');console.log(found); // { value: 'y' }
console.log(foundIndex); // 3

上面的程式碼可以獲取正確結果,但由於目標物件更接近陣列的尾部,如果我們使用findLast()和findLastIndex()方法來從陣列的末尾進行搜尋,很可能能夠顯著提升程式的執行效率。透過這種方式,我們可以更快地找到所需的元素或索引,從而最佳化程式碼效能。

const letters = [
  { value: 'v' },
  { value: 'w' },
  { value: 'x' },
  { value: 'y' },
  { value: 'z' },
];const found = letters.findLast((item) => item.value === 'y');
const foundIndex = letters.findLastIndex((item) => item.value === 'y');console.log(found); // { value: 'y' }
console.log(foundIndex); // 3

在一些特定的使用場景中,我們可能需要從陣列的末尾開始搜尋來獲取準確的元素。舉個例子,假設我們要查詢數字列表中的最後一個偶數,使用find()或findIndex()方法可能會導致錯誤的結果:

const nums = [7, 14, 3, 8, 10, 9];
// 給出 14,而不是 10
const lastEven = nums.find((value) => value % 2 === 0);
// 給出 1,而不是 4 
const lastEvenIndex = nums.findIndex((value) => value % 2 === 0);console.log(lastEven); // 14
console.log(lastEvenIndex); // 1

如果我們在呼叫reverse()方法之前使用陣列的slice()方法建立新的陣列副本,就可以避免不必要地改變原始陣列的順序。然而,在處理大型陣列時,這種方法可能會導致效能問題,因為需要複製整個陣列。

此外,findIndex()方法在反轉陣列時仍然無法達到預期效果,因為元素的反轉會導致它們在原始陣列中的索引改變。為了獲取元素的原始索引,我們需要進行額外的計算,這意味著需要編寫更多的程式碼來處理這種情況。

const nums = [7, 14, 3, 8, 10, 9];
// 在呼叫reverse()之前使用展開語法複製整個陣列
// calling reverse()
const reversed = [...nums].reverse();
// 正確給出 10 
const lastEven = reversed.find((value) => value % 2 === 0);
// 給出 1,而不是 4 
const reversedIndex = reversed.findIndex((value) => value % 2 === 0);
// 需要重新計算得到原始索引
const lastEvenIndex = reversed.length - 1 - reversedIndex;console.log(lastEven); // 10
console.log(reversedIndex); // 1
console.log(lastEvenIndex); // 4

使用findLast()和findLastIndex()方法在需要查詢陣列中最後一個符合條件的元素或索引時非常實用。它們能夠準確地定位目標物件,並且從陣列末尾開始搜尋,提供了高效的解決方案。

const nums = [7, 14, 3, 8, 10, 9];const lastEven = nums.findLast((num) => num % 2 === 0);
const lastEvenIndex = nums.findLastIndex((num) => num % 2 === 0);console.log(lastEven); // 10
console.log(lastEvenIndex); // 4

結論

ES13 為 JavaScript 帶來了一系列令人振奮的新功能,我們已經有幸見識了它們的魅力。透過運用這些功能,開發人員的工作效率將得到極大提升,同時也能以更加簡潔、明晰的方式書寫出更加純淨、精煉的程式碼。這些新特性為我們帶來了更大的靈活性和便利性,使得我們的開發過程更加高效、愉悅。

原文連結:https://medium.com/coding-beauty/es13-javascript-features-eed...

擴充套件連結:

高階SQL分析函式-如何用視窗函式進行排名計算

3D模型+BI分析,打造全新的互動式3D視覺化大屏開發方案

React + Springboot + Quartz,從0實現Excel報表自動化

相關文章