動態

詳情 返回 返回

ECMAScript 2022(ES13)初體驗 - 動態 詳情

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.findLastArray.prototype.find 的行為相同,但會從最後一個迭代到第一個。
  • Array.prototype.findLastIndexArray.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)初體驗

Add a new 評論

Some HTML is okay.