ES2022新增特性

1、Top-level await

之前await关键字只能在async函数内部使用,在外部使用就会报错: SyntaxError - SyntaxError: await is only valid in async function;

在ES13 允许在模块的顶层使用 await, 并且不再需要配合 async函数使用,它允许模块充当大型异步函数,使得我们在 ESM 模块中可以等待资源的加载,只有资源加载完毕之后才会去执行当前模块的代码。

实际运用:

a、动态加载模块

const strings = await import(`/i18n/${navigator.language}`);

这对于开发/生产拆分、国际化、环境拆分等非常有用。

b、资源初始化

const connection = await dbConnector();

配合try catch实现依赖回退

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

依赖发生故障时,可以回退到旧版本,防止程序崩溃。

2、at()

.at()是数组新增的一个方法,通过传递给定索引来获取数组元素,这个索引可以是正数也可以是负数,当索引是正数时效果和 [] 是一样的,当索引是负数时就会从数组的最后一项开始。

const arr = [1,2,3,4,5]; 
arr[arr.length-1] // 5
const arr = [1,2,3,4,5]; 
arr.at(-1) // 5

支持类型

  • String
  • Array
  • Typed Array

3、RegExp匹配索引

正则表达式,继 /i/m/g 之后,在匹配模式上新加入了 /d,使用 /d 之后,当我们再使用exec() 方法时,返回值会增加一个新的属性 indices,包含匹配对象的开始和结束索引。

相比于常见的 search()test()而言,exec() 匹配字符串的信息更丰富,它返回一个数组,其中存放匹配的结果,如果未找到匹配,则返回值为 null

let regx = /a+(?<Z>z)?/g; 
let string = "xaaaz"
let result = regx.exec(string);
// ['aaaz', 'z', index: 1, input: 'xaaaz', groups: {Z:'z'}]

(?)是 ES 2018 加入的正则语法,允许我们把部分正则内容进行 命名 并且 分组,上述例子中,/a+(?z)?/g表示匹配至少一个 a 以及 0 个或者 1 个 z ,其中包含一个命名为 Z 的捕获组,配合 exec() 方法使用时,我们可以直接获取到捕获组的信息:result.groups,如果正则中没用使用捕获组的正则语法,则 groupsundefined

尽管 exec() 返回值提供了 index 属性 来展示首次匹配的索引位置,groups 属性 提供了捕获组的信息,但在一些更高级的场景中,这些信息可能并不足够。例如,语法高亮显示 的实现不仅需要匹配的索引,还需要单个捕获组的开始和结束索引,即上述例子中 Z 捕获组的索引信息。

新特性中,使用 /d匹配模式时,exec() 方法的返回值会增加一个新的属性 indices

const re = /a+(?<Z>z)?/d;
const s = "xaaaz";
console.log("匹配结果:", re.exec(s));
//['aaaz', 'z', index: 1, input: 'xaaaz', groups: {Z:'z'}, indices: [[1,5],[4,5]]

indices属性包含了捕获组的信息,其中,[ 1 , 5 ] 为 aaaz 全部字符串的匹配信息,[ 4 , 5 ] 为 Z 捕获组的匹配索引信息。

4、Object.hasOwn()方法

在 JavaScript 中,我们可以使用 Object.prototype.hasOwnProperty() 方法来检查对象是否具有给定的属性。

但是,这种方法存在一定的问题,一方面,Object.prototype.hasOwnProperty() 方法不受保护 - 它可以通过为类定义自定义 hasOwnProperty() 方法来覆盖,该方法可能具有与Object.prototype.hasOwnProperty() 完全不同的行为。

另一个问题是,对于使用 null 原型创建的对象(使用 Object.create(null)),尝试对其调用此方法会导致错误。

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

5、error.cause

error.cause 允许我们在 new Error() 中指出造成错误的原因,可以帮助我们为错误添加更多的上下文信息,从而帮助使用者们更好地定位错误。比如说我们有的时候使用 try-catch 捕捉到的错误是一个非常底层的错误,而我们又对那块场景比较了解,我们想给外部暴露出一些有用的错误提示,此时就可以使用 error.cause 了

async function fetchData(url) {
    try{
           await fetch(url)
    } catch(error) {
        if(!url.includes(':')) {
            throw new Error('fail: url格式不对,缺少 :', {cause: error})
        }
        throw new Error('fail', {cause: error})
    }
}

try {
    fetchData('http//xxx')
} catch(error) {
    console.log(error, error.cause);
}

上面我们定义了一个 fetchData 请求方法,当接收到的 url 不符合规范时我们除了将 catch 到的底层的错误抛出去外,还会去提示用户是因为 url 缺少 : 导致 url 不符合规范。

6、数组支持逆序查找

在JS中,我们可以使用数组的find()函数来在数组中找到第一个满足某个条件的元素。同样地,我们还可以通过findIndex()函数来返回这个元素的位置。可是,无论是find()还是findIndex(),它们都是从数组的头部开始查找元素的,可是在某些情况下,我们可能有从数组后面开始查找某个元素的需要。就比如我们想要找到数组里面最后一个偶数。

在ES13中,我们就可以使用新的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

数组支持后序查找之后使得代码的可读性和正确性都提高了很多

7、类

公共字段声明

在 ES13 之前,类字段只能在构造函数中声明,我们不能在类的最外层范围内声明或定义它们。

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

在ES13中,允许我们直接将实例属性作为属性添加到类上,而无需使用构造函数方法。这样就简化了类的定义,使代码更加简洁、可读

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

注意事项

1、公共实例字段存在于每个创建的类实例上。它们要么是在 Object.defineProperty() 中添加,要么是在基类中的构造时添加(构造函数主体执行之前执行),要么在子类的 super() 返回之后添加:

class Incrementor {
  count = 0;
}

const instance = new Incrementor();
console.log(instance.count); // 0

2、未初始化的字段会自动设置为 undefined:

class Incrementor {
  count;
}

const instance = new Incrementor();
console.assert(instance.hasOwnProperty("count"));
console.log(instance.count); // undefined

3、可以进行字段的计算:

const PREFIX = "main";

class Incrementor {
  [`${PREFIX}Count`] = 0;
}

const instance = new Incrementor();
console.log(instance.mainCount); // 0

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

以前,不能在类中声明私有成员,成员通常以下划线 (_) 为前缀,表示它是私有的,但仍然可以从类外部访问和修改。

class Person {
  _firstName = 'Joseph';
  _lastName = 'Stevens';
  get name() {
    return `${this._firstName} ${this._lastName}`;
  }
}
const person = new Person();
console.log(person.name); // Joseph Stevens
// Members intended to be private can still be accessed
// from outside the class
console.log(person._firstName); // Joseph
console.log(person._lastName); // Stevens
// They can also be modified
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);
// SyntaxError: Private field '#firstName' must be
// declared in an enclosing class
console.log(person.#firstName);
console.log(person.#lastName);

由于尝试访问对象上不存在的私有字段会发生异常,因此需要能够检查对象是否具有给定的私有字段。可以使用 in 运算符来检查对象上是否有私有字段:

class Example {
  #field;

  static isExampleInstance(object) {
    return #field in object;
  }
}

静态公共字段

在 ES6 中,不能在类的每个实例中访问静态字段或方法,只能在原型中访问。ES 2022 提供了一种在 JavaScript 中使用 static 关键字声明静态类字段的方法。

class Shape {
  static color = "blue";

  static getColor() {
    return this.color;
  }

  getMessage() {
    return `color:${this.color}`;
  }
}

可以从类本身访问静态字段和方法:

console.log(Shape.color); // blue
console.log(Shape.getColor()); // blue
console.log("color" in Shape); // true
console.log("getColor" in Shape); // true
console.log("getMessage" in Shape); // false

实例不能访问静态字段和方法:

const shapeInstance = new Shape();
console.log(shapeInstance.color); // undefined
console.log(shapeInstance.getColor); // undefined
console.log(shapeInstance.getMessage()); // color:undefined

静态字段只能通过静态方法访问:

console.log(Shape.getColor()); // blue
console.log(Shape.getMessage()); //TypeError: Shape.getMessage is not a function

静态私有字段、方法和访问器

现在可以在 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

静态块

ES13允许在类中通过static关键字定义一系列静态代码块,这些代码块只会在类被创造的时候执行一次。这其实有点像一些其他的如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']
}
posted @ 2023-06-27 14:40  脏猫  阅读(155)  评论(0)    收藏  举报