2022 年 6 月 22 日,第 123 屆 ECMA 大會批准了 ECMAScript 2022 語言規範,這意味着它現在正式成為標準。下面就來看看 ECMAScript 2022 有哪些新特性!
新特性總覽
- Top-level Await
- Object.hasOwn()
- at()
- error.cause
- 正則表達式匹配索引
- 類
- ES14: Array.prototype.findLast 和 Array.prototype.findLastIndex 的提案。
Top-level Await(頂級 await)
async 和 await 在 ES2017(ES8)中引入用來簡化 Promise 操作,但是卻有一個問題,就是 await 只能在 async 內部使用, 當我們直接在最外層使用 await 的時候就會報錯:
`Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
`
沒有頂級 await 之前,當我們導入一個外部promise.js文件的時候,因為需要等待這個外部 js 執行完成再執行別的操作
// promise.js
let res = { name: "" },
num;
const np = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(456);
}, 100);
});
};
const p = async () => {
const res1 = await np();
res.name = res1;
num = res1;
};
p();
export default res;
//validate.js
import res from "./p.js";
console.log("res", res, num);
// 這時 res 和 num 都是 undefined
因為 res 和 num 需要在異步執行完成之後才能訪問它,所以我們可以加個定時器來解決
setTimeout(() => {
console.log("res3000", res, num);
}, 1000);
// res 可以正確輸出 {name: 456}
// num 還是 undefined
為什麼 res 可以正常輸出,而 num 不行?
這是因為 res 時對象,是一個引用類型,當過了 100 毫秒後,異步操作以及執行完成並且賦值了,而導出的res 和 p.js 裏面的res指向同一個地址,所以能監聽到改變,但是 num 是基本數據類型,導出的和p.js裏面的不是同一個,所以無法監聽到,故而一直是 undefined,而且在實際項目中,異步時間是不確定,所以這種方法存在一定缺陷,這時就可以使用 頂級 await 來實現
// p.js
let res = { name: "" },
num;
const np = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(456);
}, 100);
});
};
// 這裏進行改造
const res1 = await np();
res.name = res1;
num = res1;
export { res, num };
//validate.js
import { res, num } from "./p.js";
console.log("res adn num", res, num);
// 全部正常輸出
代碼自上而下執行,遇到 await 進入等待,np 函數執行完成之後進行賦值,賦值完成後導出。
await 使用部分場景
- 資源初始化:例如,等待某個文件(圖片、js(初始化變量的js)等)加載完成之後再渲染
- 依賴回退:
let depVersion;
try {
depVersion = await import(xxx/depVersion-2.0.0.min.js)
}catch {
depVersion = await import(xxx/depVersion-1.5.0.min.js)
}
- 模塊動態加載:
let myModule = 'await-module'
const module = await import(`./${myModule}`)
- 兼容性:
Object.hasOwn()
ES5:當我們檢查一個屬性時候屬於對象的時候可以使用
常用例子:
object = {firstName: '四', lastName: '李'}
for (const key in object) {
if (Object.hasOwnProperty.call(object, key)) {
const element = object[key];
console.log(element)
}
}
ES6:Object.hasOwn 特性是一種更簡潔、更可靠的檢查屬性是否直接設置在對象上的方法
常用例子:
object = {firstName: '四', lastName: '李'}
for (const key in object) {
if (Object.hasOwn(object, key)) {
const element = object[key];
console.log(element)
}
}
at()
一個 TC39 提案,向所有基本可索引類(Array、String、TypedArray)添加 .at() 方法
ES13 之前,要從可索引對象的末尾訪問值,通常的做法是寫入 arr[arr.length - N] 或者使用 arr.slice(-N)[0]
ES13:可以使用 at() 方法
// 數組
const array = [0,1,2,3,4,5];
array.at(-1) // 5
array.at(-2) // 4
// 字符串
string= 'abcdefg'
string.at(-2) // f
Error Cause
有時,對於代碼塊的錯誤需要根據其原因進行不同的處理,但錯誤的原因又較為相似(例如:錯誤的類型和消息均相同)。
// ES13 之前通常用以下幾種方式處理錯誤
async function errFunc() {
const rawResource = await axios('/testError')
.catch(err => {
// 第一種
throw new Error('我的錯誤信息:', err.message);
// 第二種,需要連接錯誤信息
const wrapErr = new Error('Download raw resource failed');
// 自己創建 一個 cause 屬性來接收錯誤上下文
wrapErr.cause = '錯誤原因:' + err;
throw wrapErr;
// 第三種,需要連接錯誤信息
class CustomError extends Error {
constructor(msg, cause) {
super(msg);
// 自己創建 一個 cause 屬性來接收錯誤上下文
this.cause = cause;
}
}
throw new CustomError('Download raw resource failed', err);
});
}
try {
const res = await errFunc()
}catch (err) {
console.log(err)
console.log(err.cause)
}
// 第一種輸出:Uncaught Error: 我的錯誤信息:Failed to fetch
// 第一種輸出:undefined
// 第二種輸出:Uncaught Error: 我的錯誤信息
// 第二種輸出:錯誤原因: err
// 第三種:Uncaught Error: 我的錯誤信息
// 第三種輸出:錯誤原因: err
正則表達式匹配索引
給正則表達式添加修飾符 d,會生成匹配對象,記錄每個組捕獲的開始和結束索引,由於 /d 標識的存在,m1 還有一個屬性 .indices,它用來記錄捕獲的每個編號組
// ?<m>n:命名分組,m 為組名稱,n 為正則表達式
const re1 = /a+(?<Z>z)?/d;
// indices are relative to start of the input string:
const s1 = "xaaaz";
const m1 = re1.exec(s1);
console.log(m1.indices)
在做字符串切割時,可以通過修飾符 d 匹配出索引範圍從而進行字符切割
// 使用
s1.slice(...m1.indices[0]) // aaaz
// 相當於
s1.slice(1, 5) // aaaz
// 按組切割
s1.slice(...m1.indices.groups['Z']) // z
類 class
公共實例字段
在 ES13 之前,在定義類的屬性時,需要在構造函數中定義了實例字段和綁定方法
class myClass {
constructor() {
this.count = 1
this.increment = this.increment.bind(this);
}
increment() {
this.count += 1
}
}
ES 13 可以使用公共實例字段,這樣就簡化了類的定義,使代碼更加簡潔、可讀
class myClass {
count = 1
increment = () => {
this.count += 1
}
}
私有實例字段
默認情況下,class 中所有屬性都是公共的,可以在 class 之外進行修改,例如
class myClass {
count = 1
setCount = () => {
this.count += 1
}
}
const es13 = new myClass()
es13.count = 5
// myClass {count: 5, setCount: ƒ}count: 5setCount: () => { this.count += 1 }[[Prototype]]: Object
通過上面的例子可以看到,當我們直接設置 count 屬性的時候,是直接跳過 setCount 進行設置的,有時候我們並不想這樣,所以可以使用私有實例字段,用法很簡單,只需要在私有字段添加 # 就可以實現,當然了,在調用的時候我們也應該加上 # 進行調用,如下:
class myClass {
#count = 1
setCount = () => {
this.#count += 1
}
}
const es13 = new myClass()
es13.setCount() // 正常修改,每執行執行一次 setCount 方法後 #count的值每一次都加1
// 直接修改私有屬性
es13.#count = 5
// 報錯:Uncaught SyntaxError: Private field '#count' must be declared in an enclosing class
可以看到,當我們直接修改私有屬性之後,瀏覽器直接拋出錯誤:Uncaught SyntaxError: Private field '#count' must be declared in an enclosing class
私有方法
都有私有屬性了,怎麼能少了私有方法呢,方法和屬性一下只有加上 # 即可:
class myClass {
#count = 1
#setCount = () => {
this.#count += 1
}
newSetCount = () => {
this.#setCount()
}
}
const es13 = new myClass()
es13.#setCount()
// 直接調用私有方法報錯:Uncaught SyntaxError: Private field '#setCount' must be declared in an enclosing class
//通過公共方法 newSetCount 調用
es13.newSetCount()
// VM436:9 Uncaught TypeError: Cannot read private member #setCount from an object whose class did not declare it
靜態公共字段、靜態私有字段、靜態私有方法
與私有實例字段和方法一樣,靜態私有字段和方法也使用哈希#前綴來定義
class myClass {
//靜態公共字段
static color = 'blue'
// 靜態私有字段
static #count = 1
// 靜態私有方法
static #setCount = () => {
this.#count += 1
}
newSetCount = () => {
this.#setCount()
}
}
const es13 = new myClass()
實例 es13 上面只有 newSetCount() 方法
es13.newSetCount()
// 報錯:Uncaught SyntaxError: Private field '#setCount' must be declared in an enclosing class
私有靜態字段有一個限制:只有定義私有靜態字段的類才能訪問該字段。這可能在使用 this 時導致出乎意料的情況, 所有我們需要改一下
class myClass {
// 靜態私有字段
static #count = 1
// 靜態私有方法
static #setCount = () => {
// 實例化之後,this 不再指向 myClass,所有需要改成 myClass 類調用
myClass.#count += 1
}
newSetCount = () => {
// 實例化之後,this 不再指向 myClass,所有需要改成 myClass 類調用
myClass.#setCount()
}
}
const es13 = new myClass()
es13.newSetCount()
// 成功
類靜態塊
在以前,如果我們希望在初始化期間像 try…catch 一樣進行異常處理,就不得不在類之外編寫此邏輯。該規範就提供了一種在類聲明/定義期間評估靜態初始化代碼塊的優雅方法,可以訪問類的私有字段。
ES13之前
class Person {
static EEEOR = "error"
static SUCCESS_TYPE = "success_type";
constructor() {
// ...
}
try {
// ...
} catch {
// ...
}
}
上面代碼直接報錯:Uncaught SyntaxError: Unexpected token '{'
ES13 : 直接將 try...cathc 使用 static 包裹起來即可
class Person {
static EEEOR = "error"
static SUCCESS_TYPE = "success_type";
constructor() {
// ...
}
static {
try {
// ...
} catch {
// ...
}
}
}
ES14 新提案
- Array.prototype.findLast
- Array.prototype.findLastIndex
Tips:
- Array.prototype.findLast 和 Array.prototype.find 的行為相同,但會從最後一個迭代到第一個。
- Array.prototype.findLastIndex 和 Array.prototype.findIndex的行為相同,但會從最後一個迭代到第一個。
const array = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }];
array.find(n => n.value % 2 === 1); // { value: 1 }
array.findIndex(n => n.value % 2 === 1); // 0
// ======== Before the proposal ===========
// find
[...array].reverse().find(n => n.value % 2 === 1); // { value: 3 }
// findIndex
array.length - 1 - [...array].reverse().findIndex(n => n.value % 2 === 1); // 2
array.length - 1 - [...array].reverse().findIndex(n => n.value === 42); // should be -1, but 4
// ======== In the proposal ===========
// find
array.findLast(n => n.value % 2 === 1); // { value: 3 }
// findIndex
array.findLastIndex(n => n.value % 2 === 1); // 2
array.findLastIndex(n => n.value === 42); // -1
原文鏈接:ECMAScript 2022(ES13)初體驗