ES2022 新特性全面梳理

1,251 阅读2分钟

ES2022 新特性全面梳理

1. Top-level Await

  • 变化前:

    在ES2017中,引入了 async 函数和 await 关键字,以简化 Promise 的使用,但是 await 关键字只能在 async 函数内部使用。 

  • 变化后:

    顶层 await 允许我们在 async 函数外面使用 await 关键字。它允许模块充当大型异步函数,通过顶层 await,这些 ECMAScript 模块可以等待资源加载。这样其他导入这些模块的模块在执行代码之前要等待资源加载完再去执行。

  • 应用场景:

  1. 动态加载模块
const strings = await import(`/i18n/${navigator.language}`);
  1. 资源初始化
const connection = await dbConnector();
  1. 依赖回退
let translations;
try {
  translations = await import('https://app.fr.json');
} catch {
  translations = await import('https://fallback.en.json');
}

2. Object.hasOwn()

  • 变化前:

使用 Object.prototype.hasOwnProperty() 来检查一个属性是否属于对象。

const example = {
  property: '123'
};


console.log(Object.prototype.hasOwnProperty.call(example, 'property'));
  • 变化后:

Object.hasOwn 特性是一种更简洁、更可靠的检查属性是否直接设置在对象上的方法。

console.log(Object.hasOwn(example, 'property'));
  • 应用场景:

    Object.hasOwn是对于Object.prototype.hasOwnProperty的改良。

    【一个典型场景】Object.create(null) 会创建一个不从 Object.prototype 继承的对象,这使得Object.prototype 上的方法无法访问,需要通过prototype属性进行访问。

const object = Object.create(null)
object.hasOwnProperty("foo") // Uncaught TypeError: Object.create(...).hasOwnProperty is not a function
Object.prototype.hasOwnProperty.call(object, 'foo') // false
Object.hasOwn(object, 'foo') // false

3. 数组方法 at() 

  • 变化前:
const arr = [1,2,3,4,5];
console.log(arr[arr.length-2)); // 4
  • 变化后:
console.log(arr.at(-2)); // 4
  • 应用场景:

    支持读取给定索引处的元素。它可以接受负索引来从给定数据类型的末尾读取元素。

4. error.cause

  • 变化前:

    Error不能指定引起异常的原因

try {
  doSomeComputationThatThrowAnError() 
} catch (error) {
  throw new Error('I am the result of another error')
}      
  • 变化后:

    Error新增cause字段用于指定原因

try {
  doSomeComputationThatThrowAnError() 
} catch (error) {
  throw new Error('I am the result of another error', { cause: error })
}           
  • 应用场景:

    新特性是在 Error 构造函数上添加一个附加选项参数 cause,其值将作为属性分配给错误实例。因此,可以将错误链接起来。

5. 正则表达式匹配索引

  • 变化前

    以前,只能在字符串匹配操作期间获得一个包含提取的字符串和索引信息的数组。在某些情况下,这是不够的

const matchObj = /(a+)(b+)/.exec('aaaabb');

  • 变化后

    该特性允许我们利用 d 字符来表示我们想要匹配字符串的开始和结束索引。因此,在这个规范中,如果设置标志 /d,将额外获得一个带有开始和结束索引的数组。

const matchObj = /(a+)(b+)/d.exec('aaaabb');

此外,还支持使用命名组:

const matchObj = /(?<as>a+)(?<bs>b+)/d.exec('aaaabb');


console.log(matchObj.groups.as);  // 'aaaa'
console.log(matchObj.groups.bs);  // 'bb'

  • 应用场景

匹配索引的一个重要用途就是指向语法错误所在位置的解析器。下面的代码解决了一个相关问题:它指向引用内容的开始和结束位置。

const reQuoted = /“([^”]+)”/dgu;
function pointToQuotedText(str) {
  const startIndices = new Set();
  const endIndices = new Set();
  for (const match of str.matchAll(reQuoted)) {
    const [start, end] = match.indices[1];
    startIndices.add(start);
    endIndices.add(end);
  }
  let result = '';
  for (let index=0; index < str.length; index++) {
    if (startIndices.has(index)) {
      result += '[';
    } else if (endIndices.has(index+1)) {
      result += ']';
    } else {
      result += ' ';
    }
  }
  return result;
}


console.log(pointToQuotedText('They said “hello” and “goodbye”.'));
// '           [   ]       [     ]  '

6. 类

class MyClass {
  instancePublicField = 1;
  static staticPublicField = 2;


  #instancePrivateField = 3;
  static #staticPrivateField = 4;


  #nonStaticPrivateMethod() {}
  get #nonStaticPrivateAccessor() {}
  set #nonStaticPrivateAccessor(value) {}


  static #staticPrivateMethod() {}
  static get #staticPrivateAccessor() {}
  static set #staticPrivateAccessor(value) {}


  static {
    // 静态初始化代码块
  }
}

1)公共字段:

  • 变化前:
class X {
  constructor() {
    this.a = 123;
  }
}
  • 变化后:-
class X { 
    a = 123; 
}

2)私有实例字段、方法和访问器

  • 变化前:

在ES2022之前,并没有实际意义上的私有字段。大家形成一种默契,通常用下划线_开头的字段名来表示私有字段,但是这些字段还是可以手动更改的。

  • 变化后:
class X {
      #a = 123;
      static isXInstance(object) {
          return #a in object;
      }
}
const x = new X();
x.#a  // Uncaught SyntaxError: Private field '#a' must be declared in an enclosing class
X.isXInstance(x); // 判断是否包含私有属性

ES2022给我们提供了更加安全便捷的私有字段定义方法,就是以#开头命名的字段,都会被当成私有字段,在class外部是没办法直接读取、修改这些私有字段的。

3) 静态公共字段

ES 2022 提供了一种在 JavaScript 中使用 static 关键字声明静态类字段的方法。

1、类本身直接访问静态变量和方法

2、继承的类也可直接访问

class Shape {
      static color = 'blue';
      static getColor() {
            return this.color;
      }
      getMessage() {
            return `color:${Shape.getColor()}` ;
       }
}
// 1、类本身直接访问静态变量和方法
console.log(Shape.color); // blue
console.log(Shape.getColor()); // blue
// 实例、外部均不可访问静态方法和变量;


// 2、继承的类也可直接访问
class Rectangle extends Shape { }


console.log(Rectangle.color); // blue
console.log(Rectangle.getColor()); // blue

4)静态私有字段和方法

与私有实例字段和方法一样,静态私有字段和方法也使用哈希 (#) 前缀来定:

  1. 只有定义私有静态字段的类才能访问该字段。
  2. 继承的类无法访问
class Shape {
  static #color = 'blue';
  static #getColor() {
    return this.#color;
  }
  getMessage() {
    return `color:${Shape.#getColor()}` ;
  }
}
// 只有定义私有静态字段的类才能访问该字段
const shapeInstance = new Shape();
shapeInstance.getMessage(); // color:blue
// 继承的类无法访问
class Rectangle extends Shape {}
console.log(Rectangle.getMessage()); // Uncaught TypeError: Cannot read private member #color from an object whose class did not declare it

5)类静态初始化块

变化前,我们初始化类的静态变量只能在定义类的时候去做,而对于一些无法显示声明的变量,我们往往使用一个函数去初始化。

const doSomethingWith = (x) => {
    return {y: x++, z: x**2}
}
// without static blocks:
class C {
  static x = 3;
  static y;
  static z;
}


try {
  const obj = doSomethingWith(C.x);
  C.y = obj.y
  C.z = obj.z;
}
catch {
  C.y = 0;
  C.z = 0;
}

变化后,可以在类内部开辟一个专门为静态成员初始化的代码块。

// with static blocks:
class C {
  static x = 3;
  static y;
  static z;
  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = 0;
      this.z = 0;
    }
  }
}

TC39 ES修订流程

官方流程

新的ECMAScript 特性必须选TC39提交提案,会经历:

  • 从阶段 0(使 TC39 能够对提案发表评论)
  • 到第 4 阶段(提议的功能已准备好添加到 ECMAScript 中)

一旦一个特性达到第 4 阶段,它就会被计划添加到 ECMAScript 中。ECMAScript 版本的功能集通常在每年的 3 月冻结。 在截止日期之后达到第 4 阶段的功能将添加到明年的 ECMAScript 版本中

提案仓库:github.com/tc39/propos…

参考资料: