面试|ECMAScript 2022 正式发布,有你了解过的吗()

目录
前言
正文
一、总览
二、详述
1、Top-level await(顶层?await)
2、Object.hasOwn()
3、at()
4、error.cause
5、正则表达式匹配索引
6、类
总结
前言 2022年6月22日,第123届 ECMA 大会批准了 ECMAScript 2022 语言规范,这意味着它现在正式成为标准。下面就来看看 ECMAScript 2022 有哪些新特性,是否有你了解过的。
正文 一、总览 1、Top-level await(顶层await)
2、Object.hasOwn()
3、at()
4、error.cause
5、正则表达式匹配索引
6、类
二、详述 1、Top-level await(顶层await)
在 ES2017 中,引入了 async await,以简化 Promise 的使用,但是 await 关键字只能在 async 函数内使用。如果在异步函数之外使用 await 就会报错。
顶层 await 允许我们在 async 函数外使用 await 关键字。它允许模块充当大型异步函数,通过顶层 await,这些模块可以等待资源加载,这样其他导入这些模块的模块在执行代码之前要等待资源加载完才会去执行。
之前由于 await 仅在 async 函数中使用,因此模块需通过将代码包装在 async 函数来在代码中包含await:

// test.js let users; export const fetchUsers = async () => { const res = await fetch('https://www.leoTest.com/users'); users = res.json(); } fetchUsers(); export { users }; // usingAwait.js import { users } from './test.js'; console.log('users: ', users); console.log('usingAwait module');

这样会有一个缺点,直接导入的 users 会是 undefined。如果我们需要访问得到它,需要在异步执行完成之后:
// usingAwait.js import { users } from './test.js'; console.log('users:', users); // 直接访问,undefinedsetTimeout(() => { console.log('users:', users); // 等待异步执行完成,再进行访问 }, 100); console.log('usingAwait module');

当然,这种方法并不完全实现,因为如果异步函数执行花费的时间超过100毫秒,它就不会起作用了,users 仍然是 undefined。
还有另一个方法是导出一个 Promise,让导入模块知道数据已经准备好了:
// test.js export default (async () => {// 导出一个 Promise const res = await fetch('https://www.leoTest.com/users'); users = res.json(); })(); export { users }; // usingAwait.js import Promise, { users } from './test.js'; Promise.then(() => { console.log('usingAwait module'); setTimeout(() => console.log('users:', users), 100); });

虽然这种方法似乎是会得出预期的结果,但也有一定的局限性:导入模块必须了解这种模式才能正确使用它。
而 top-level await(顶层await)可以解决这些问题:
// test.js const res = await fetch('https://www.leoTest.com/users'); // 使用顶层 await const users = res.json(); export { users }; // usingAwait.js import { users } from './test.js'; console.log(users); console.log('usingAwait module');

此时的 users 必须等到异步执行完毕才会访问。
顶层 await 在以下几种场景中将非常有用:
①动态加载模块
const str = await import(`/i18n/${navigator.language}`);

②资源初始化
const con = await dbConnector();

③依赖回退
let translations; try { translations = await import('https://app.fr.json'); } catch { translations = await import('https://fallback.en.json'); }

2、Object.hasOwn()
在此之前,我们可以使用 Object.prototype.hasOwnProperty() 来检查一个属性是否属于该对象。
Object.hasOwn() 特性是一种更简洁、更可靠的检查属性是否直接设置在该对象上的方法:
const example = { name: 'leo' }; console.log(Object.prototype.hasOwnProperty.call(example, 'name')); // 更简介的方式 console.log(Object.hasOwn(example, 'name'));

3、at()
at() 是一个数组方法,用于通过给定索引来获取数组元素。当给定索引为正数时,这种新方法与使用中括号表示法访问具有相同的行为。当给出负整数索引时,就会从数组的最后一项开始检索:
const arr = [0,1,2,3,4,5]; console.log(arr[0]); // 0 console.log(arr.at(0)); // 0console.log(arr[arr.length - 1]); // 5 console.log(arr.at(-1)); // 5console.log(arr[arr.lenght - 2]); // 4 console.log(arr.at(-2)); // 4

除了数组,字符串也同样适用:
const str = "hello world"; console.log(str[str.length - 1]); // d console.log(str.at(-1)); // d

4、error.cause
在 ES2022 规范中,new Error()中可以指定导致它的原因:
function readFiles(filePaths) { return filePaths.map( (filePath) => { try { // ··· } catch (error) { throw new Error( `While processing ${filePath}`, {cause: error} ); } }); }

5、正则表达式匹配索引
该特性允许我们利用 d 字符来表示我们想要匹配字符串的开始和结束索引。以前,只能在字符串匹配操作期间获得一个包含提取的字符串和索引信息的数组。在某些情况下,这是不够的。因此,在这个规范中,如果设置标志 /d,将额外获得一个带有开始和结束索引的数组。
const matchObj = /(a+)(b+)/d.exec('aaaabb'); console.log(matchObj[1]); // 'aaaa' console.log(matchObj[2]); // 'bb'

由于 /d 标识的存在,matchObj 还有一个属性 .indices,它用来记录捕获的每个编号组:
console.log(matchObj.indices[1]); // [0, 4] console.log(matchObj.indices[2]); // [4, 6]

6、类
① 公共实例字段
公共类字段允许我们使用赋值运算符(=)将实例属性添加到类定义中。下面是一个计数器的例子:
import React, { Component } from "react"; export class Incrementor extends Component { constructor() { super(); this.state = { count: 0, }; this.increment = this.increment.bind(this); }increment() { this.setState({ count: this.state.count + 1 }); }render() { return ( ); } }

在这个例子中,在构造函数中定义了实例字段和绑定方法,通过新的类语法,可以使代码更加直观。新的公共类字段语法允许我们直接将实例属性作为属性添加到类上,而无需使用构造函数方法。这样就简化了类的定义,使代码更加简洁、可读:
import React from "react"; export class Incrementor extends React.Component { state = { count: 0 }; increment = () => this.setState({ count: this.state.count + 1 }); render = () => ( ); }

有些小伙伴可能已经发现了,这个功能很早就可以使用了呀。但是它现在还不是标准的 ECMAScript,默认是不开启的,如果使用create-react-app创建 react 项目,那么它默认是启用的,否则我们必须使用正确的 babel 插件才能正常使用(@babel/preset-env)。
以下是一些公共实例字段的注意事项:
⑴ 公共实例字段存在于每个创建的类实例上。它们要么是在 Object.defineProperty() 中添加,要么是在基类中的构造时添加,要么在子类的 super() 返回之后添加
class Incrementor { count = 0 }const instance = new Incrementor(); console.log(instance.count); // 0

⑵未初始化的字段会自动设置为 undefined
class Incrementor { count }const instance = new Incrementor(); console.log(instance.count); // undefined

⑶ 可以进行字段的计算
const PREFIX = 'main'; class Incrementor { [`${PREFIX}Count`] = 0 }const instance = new Incrementor(); console.log(instance.mainCount); // 0

②私有实例字段、方法和访问器
默认情况下,ES6 中所有属性都是公共的,可以在类外检查或修改:
class TimeTracker { name = 'leo'; project = 'blog'; hours = 0; set addHours(hour) { this.hours += hour; }get timeSheet() { return `${this.name} works ${this.hours || 'nothing'} hours on ${this.project}`; } }let person = new TimeTracker(); person.addHours = 2; // 标准 setter person.hours = 4; // 绕过 setter 进行设置 person.timeSheet; // 'leo works 4 hours on blog'

可以看到,在类中没有任何措施可以防止在不调用 setter 的情况下更改属性。
而私有类字段将使用 # 前缀定义,在上面的示例中,可以修改它以包含私有类字段,以防止在类方法之外更改属性:
class TimeTracker { name = 'leo'; project = 'blog'; #hours = 0; // 私有类字段set addHours(hour) { this.#hours += hour; }get timeSheet() { return `${this.name} works ${this.#hours || 'nothing'} hours on ${this.project}`; } }let person = new TimeTracker(); person.addHours = 4; // 标准 setter person.timeSheet// 'leo works 4 hours on blog'

当尝试在 setter 方法之外修改私有类字段时,就会报错:
person.hours = 4; // Error Private field '#hours' must be declared in an enclosing class

还可以将方法或 getter/setter 设为私有,只需要给这些方法名称前面加 # 即可:
class TimeTracker { name = 'leo'; project = 'blog'; #hours = 0; // 私有类字段set #addHours(hour) { this.#hours += hour; }get #timeSheet() { return `${this.name} works ${this.#hours || 'nothing'} hours on ${this.project}`; }constructor(hours) { this.#addHours = hours; console.log(this.#timeSheet); } }let person = new TimeTracker(4); // 'leo works 4 hours on blog'

③静态公共字段
在 ES6 中,不能在类的每个实例中访问静态字段或方法,只能在原型中访问。ES2022 提供了一种在 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

这里的Shape.getMessage()就报错了,因为getMessage不是一个静态函数,所以它不能通过类名Shape访问。可以通过以下方式来解决这个问题:
getMessage() { return `color:${Shape.color}` ; }

静态字段和方法是从父类继承的:
class Rectangle extends Shape { }// 继承console.log(Rectangle.color); // blue console.log(Rectangle.getColor()); // blue console.log('color' in Rectangle); // true console.log('getColor' in Rectangle); // true console.log('getMessage' in Rectangle); // false

④静态私有字段和方法
与私有实例字段和方法一样,静态私有字段和方法也使用哈希 # 前缀来定义:
class Shape { static #color = 'blue'; static #getColor() { return this.#color; }getMessage() { return `color:${Shape.#getColor()}` ; } } const shapeInstance = new Shape(); shapeInstance.getMessage(); // color:blue

私有静态字段有一个限制,即只有定义私有静态字段的类才能访问该字段。这可能在使用this时导致出乎意料的情况:
class Shape { static #color = 'blue'; static #getColor() { return this.#color; } static getMessage() { return `color:${this.#color}` ; } getMessageNonStatic() { return `color:${this.#getColor()}` ; } }class Rectangle extends Shape {}console.log(Rectangle.getMessage()); // Uncaught TypeError: Cannot read private member #color from an object whose class did not declare itconst rectangle = new Rectangle(); console.log(rectangle.getMessageNonStatic()); // TypeError: Cannot read private member #getColor from an object whose class did not declare it

在这个例子中,this指向的是Rectangle类,它无权访问私有字段#color。当我们尝试调用Rectangle.getMessage()时,它无法读取#color并抛出了TypeError。可以这样来进行修改:
class Shape { static #color = 'blue'; static #getColor() { return this.#color; } static getMessage() { return `${Shape.#color}`; } getMessageNonStatic() { return `color:${Shape.#getColor()} color`; } }class Rectangle extends Shape {} console.log(Rectangle.getMessage()); // color:blueconst rectangle = new Rectangle(); console.log(rectangle.getMessageNonStatic()); // color:blue

总结 以上就是 ES2022 发布的几个新特性,有你了解过的吗?
如文章有不恰当之处,请不吝赐教。
【面试|ECMAScript 2022 正式发布,有你了解过的吗()】先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

    推荐阅读