es2022正式发布!有哪些新特性?(代码片段)

前端开发博客 前端开发博客     2022-12-12     556

关键词:

2022 年 6 月 22 日,第 123 届 ECMA 大会批准了 ECMAScript 2022 语言规范,这意味着它现在正式成为标准。下面就来看看 ECMAScript 2022 有哪些新特性!

总览:

  1. Top-level Await

  2. Object.hasOwn()

  3. at()

  4. error.cause

  5. 正则表达式匹配索引

1. Top-level Await

在ES2017中,引入了 async 函数和 await 关键字,以简化 Promise 的使用,但是 await 关键字只能在 async 函数内部使用。尝试在异步函数之外使用 await 就会报错:SyntaxError - SyntaxError: await is only valid in async function

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

由于 await 仅在 async 函数中可用,因此模块可以通过将代码包装在 async 函数中来在代码中包含 await

// a.js
  import fetch  from "node-fetch";
  let users;

  export const fetchUsers = async () => 
    const resp = await fetch('https://jsonplaceholder.typicode.com/users');
    users =  resp.json();
  
  fetchUsers();

  export  users ;

  // usingAwait.js
  import users from './a.js';
  console.log('users: ', users);
  console.log('usingAwait module');

我们还可以立即调用顶层async函数(IIAFE):

import fetch  from "node-fetch";
  (async () => 
    const resp = await fetch('https://jsonplaceholder.typicode.com/users');
    users = resp.json();
  )();
  export  users ;

这样会有一个缺点,直接导入的 usersundefined,需要在异步执行完成之后才能访问它:

// usingAwait.js
import users from './a.js';

console.log('users:', users); // undefined

setTimeout(() => 
  console.log('users:', users);
, 100);

console.log('usingAwait module');

当然,这种方法并不安全,因为如果异步函数执行花费的时间超过100毫秒, 它就不会起作用了,users 仍然是 undefined

另一个方法是导出一个 promise,让导入模块知道数据已经准备好了:

//a.js
import fetch  from "node-fetch";
export default (async () => 
  const resp = await fetch('https://jsonplaceholder.typicode.com/users');
  users = resp.json();
)();
export  users ;

//usingAwait.js
import promise, users from './a.js';
promise.then(() =>  
  console.log('usingAwait module');
  setTimeout(() => console.log('users:', users), 100); 
);

虽然这种方法似乎是给出了预期的结果,但是有一定的局限性:导入模块必须了解这种模式才能正确使用它

而顶层await就可以解决这些问题:

// a.js
  const resp = await fetch('https://jsonplaceholder.typicode.com/users');
  const users = resp.json();
  export  users;

  // usingAwait.js
  import users from './a.mjs';

  console.log(users);
  console.log('usingAwait module');

顶级 await 在以下场景中将非常有用:

  • 动态加载模块:

const strings = await import(`/i18n/$navigator.language`);
  • 资源初始化:

const connection = await dbConnector();
  • 依赖回退:

let translations;
try 
  translations = await import('https://app.fr.json');
 catch 
  translations = await import('https://fallback.en.json');

该特性的浏览器支持如下:

2. Object.hasOwn()

在ES2022之前,可以使用 Object.prototype.hasOwnProperty() 来检查一个属性是否属于对象。

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

const example = 
  property: '123'
;

console.log(Object.prototype.hasOwnProperty.call(example, 'property'));
console.log(Object.hasOwn(example, 'property'));

该特性的浏览器支持如下:

3. at()

at() 是一个数组方法,用于通过给定索引来获取数组元素。当给定索引为正时,这种新方法与使用括号表示法访问具有相同的行为。当给出负整数索引时,就会从数组的最后一项开始检索:

const array = [0,1,2,3,4,5];

console.log(array[array.length-1]);  // 5
console.log(array.at(-1));  // 5

console.log(array[array.lenght-2]);  // 4
console.log(array.at(-2));  // 4

除了数组,字符串也可以使用at()方法进行索引:

const str = "hello world";

console.log(str[str.length - 1]);  // d
console.log(str.at(-1));  // d

4. error.cause

在 ECMAScript 2022 规范中,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]

我们还可以使用命名组:

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

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

这里给两个字符匹配分别命名为asbs,然后就可以通过groups来获取到这两个命名分别匹配到的字符串。

它们的索引存储在 matchObj.indices.groups 中:

console.log(matchObj.indices.groups.as);  // [0, 4]
console.log(matchObj.indices.groups.bs);  // [4, 6]

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

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. 类

(1)公共实例字段

公共类字段允许我们使用赋值运算符 (=) 将实例属性添加到类定义中。下面是一个计数器的例子:

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 (
      <button onClick=this.increment>Increment: this.state.count</button>
    );
  

在这个例子中,在构造函数中定义了实例字段和绑定方法,通过新的类语法,可以使代码更加直观。新的公共类字段语法允许我们直接将实例属性作为属性添加到类上,而无需使用构造函数方法。这样就简化了类的定义,使代码更加简洁、可读:

import React from "react";

export class Incrementor extends React.Component 
  state =  count: 0 ;

  increment = () => this.setState( count: this.state.count + 1 );

  render = () => (
    <button onClick=this.increment>Increment: this.state.count</button>
  );

有些小伙伴可能就疑问了,这个功能很早就可以使用了呀。但是它现在还不是标准的 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.assert(instance.hasOwnProperty('count'));
console.log(instance.count);  // undefined
  • 可以进行字段的计算:

const PREFIX = 'main';

class Incrementor 
  [`$PREFIXCount`] = 0


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

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

默认情况下,ES6 中所有属性都是公共的,可以在类外检查或修改。下面来看一个例子:

class TimeTracker 
  name = 'zhangsan';
  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;

可以看到,在类中没有任何措施可以防止在不调用 setter 的情况下更改属性。

而私有类字段将使用哈希#前缀定义,从上面的示例中,可以修改它以包含私有类字段,以防止在类方法之外更改属性:

class TimeTracker 
  name = 'zhangsan';
  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     // zhangsan 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 = 'zhangsan';
  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); // zhangsan works 4 hours on blog

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

class Example 
  #field

  static isExampleInstance(object) 
    return #field in object;
  

(3)静态公共字段

在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

这里的 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

(4)静态私有字段和方法

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

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 it
const 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:blue
const rectangle = new Rectangle();
console.log(rectangle.getMessageNonStatic()); // color:blue

(5)类静态初始化块

静态私有和公共字段只能让我们在类定义期间执行静态成员的每个字段初始化。如果我们需要在初始化期间像 try…catch 一样进行异常处理,就不得不在类之外编写此逻辑。该规范就提供了一种在类声明/定义期间评估静态初始化代码块的优雅方法,可以访问类的私有字段。

先来看一个例子:

class Person 
    static GENDER = "Male"
    static TOTAL_EMPLOYED;
    static TOTAL_UNEMPLOYED;

    try 
        // ...
     catch 
        // ...
    

上面的代码就会引发错误,可以使用类静态块来重构它,只需将try...catch包裹在 static 中即可:

class Person 
    static GENDER = "Male"
    static TOTAL_EMPLOYED;
    static TOTAL_UNEMPLOYED;
    
  static 
   try 
        // ...
     catch 
        // ...
    
  

此外,类静态块提供对词法范围的私有字段和方法的特权访问。这里需要在具有实例私有字段的类和同一范围内的函数之间共享信息的情况下很有用。

let getData;

class Person 
  #x
  
  constructor(x) 
    this.#x =  data: x ;
  

  static 
    getData = (obj) => obj.#x;
  


function readPrivateData(obj) 
  return getData(obj).data;


const john = new Person([2,4,6,8]);

readPrivateData(john); // [2,4,6,8]

这里,Person 类与 readPrivateData 函数共享了私有实例属性。

最后

我是小前端,欢迎大家围观我的朋友圈,搞搞技术,吹吹牛逼。我的微信:kujian89,秒添加,邀你进入 500人前端群。


 
推荐阅读

20个不容错过的ES6技巧

2022年了,这些ES7-ES12的知识点你都掌握了嘛?

你需要知道的30个ES6—ES12开发技巧!

关注公众号:前端开发博客

  1. 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF

  2. 回复「Vue脑图」获取 Vue 相关脑图

  3. 回复「思维图」获取 JavaScript 相关思维图

  4. 回复「简历」获取简历制作建议

  5. 回复「简历模板」获取精选的简历模板

  6. 回复「加群」进入500人前端精英群

  7. 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。

  8. 回复「知识点」下载高清JavaScript知识点图谱

 👍🏻 点赞 + 在看 支持小编

kotlin1.6正式发布,都有哪些新特性?(代码片段)

11月16日,Kotlin1.6正式对外发布。接下来就一起看一下在这个版本中都有哪些新的语法特性更安全的when语句(exhaustivewhenstatements)挂起函数类型可作父类(suspendingfunctionsassupertypes)普通函数转挂起函数(sus... 查看详情

es11新特性(代码片段)

写在前面ES2020(即ES11)上周(2020年6月)已经正式发布,在此之前进入Stage4的10项提案均已纳入规范,成为JavaScript语言的新特性一.特性一览ESModule迎来了一些增强:import():一种可以用动态模块标识异步引入模块的的语法import.met... 查看详情

最全的——es6有哪些新特性?(代码片段)

目录ES6新特性1、let和const2、symbol3、模板字符串3.1字符串新方法(补充)4、解构表达式4.1数组解构4.2对象解构5、对象方面5.1Map和Set5.1.1Map5.1.2Set5.3数组的新方法5.3.1Array.from()方法5.3.2includes()方法5.3.3map()、filter()方法5.3.4forEa... 查看详情

es11来了,有些新特性还是值得一用的!(代码片段)

?写在前面ES2020(即ES11)上周(2020年6月)已经正式发布,在此之前进入Stage4的10项提案均已纳入规范,成为JavaScript语言的新特性一.特性一览ESModule迎来了一些增强:import():一种可以用动态模块标识异步引入模块的的语法-由Domen... 查看详情

前端知识总结--es6新特性(代码片段)

...S6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言可以用来编写复杂的大型应用程序,成为企业级开发语言。整理下ES6的新特性,具体用法不详细描述,只作为总... 查看详情

java14有哪些新特性?

...zi译者|弯月,责编|郭芮以下为译文:Java14即将在2020年3月正式发布。Java以6个月作为新版本的发布周期,和之前的版本发布一样,JDK14预计将在语言本身和JVM级别上带来一些新特性。如果我们看一下特性列表,我们会注意到一些... 查看详情

xcode13正式版发布,来看看有什么新特性(代码片段)

Xcode13包括适用于iOS15、iPadOS15、tvOS15、watchOS8和macOSBigSur11.3的SDK。Xcode13ReleaseCandidate支持iOS9及更高版本、tvOS9及更高版本以及watchOS2及更高版本的设备上调试。Xcode13需要运行macOS11.3或更高版本的Mac。\\$(SRCROOT)/include/component2当使用&n... 查看详情

es6&&ecmascript2015新特性(代码片段)

...用ES6,但为了看懂别人的你也该懂点ES6的语法了…在我们正式讲解ES 查看详情

xcode13正式版发布,来看看有什么新特性(代码片段)

👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇作者丨小集来源丨小集(ID:zsxjtip)Xcode13包括适用于iOS15、iPadOS15、tvOS15、watchOS8和macOSBigSur11.3的SDK。Xcode13ReleaseCandidate支持iOS9及 查看详情

es11来了,还学得动吗?(代码片段)

写在前面ES2020(即ES11)上周(2020年6月)已经正式发布,在此之前进入Stage4的10项提案均已纳入规范,成为JavaScript语言的新特性 一.特性一览ESModule迎来了一些增强:import():一种可以用动态模块标识异步引入模块的的语法impo... 查看详情

es8---新特性(代码片段)

ES8尚未发布(2017年1月),下面是它已经完成起草的一些特性:Object.values()Object.entries()padStart()padEnd()Object.getOwnPropertyDescriptors()函数参数列表结尾允许逗号Async/AwaitObject.values()不使用ES8使用Object.keys()遍历对象的属性值,需要通过属性... 查看详情

发布倒计时!jdk11为我们带来哪些新特性?(代码片段)

...这一阶段将会修复P1–P2级BUG,之后,JDK11预定于今年9月25日发布。确定发布的17个JEP如下,其中包括14个新特性以及3个移除的功能:  181:Nest-BasedAccessControl(基于嵌套的访问控制)  309:DynamicCla 查看详情

es8新特性

ES8已经正式发布了,其新特性有:字符串填充、异步函数与共享内存与原子操作等。本文对着三个特性进行深入的解析。当然还有其他的一些特性。1.字符串填充     ES8中为字符串添加了新的内置函数padStart()、... 查看详情

microsoftsqlserver2022新特性之t-sql语言增强(代码片段)

MicrosoftSQLServer2022已经正式发布,可以下载使用。本文给大家介绍一下该版本中的部分T-SQL新功能。窗口函数增强新版本中的窗口函数支持命名窗口(WINDOW)子句,可以利用该子句定义窗口变量,然后在OVER子句... 查看详情

microsoftsqlserver2022新特性之t-sql语言增强(代码片段)

MicrosoftSQLServer2022已经正式发布,可以下载使用。本文给大家介绍一下该版本中的部分T-SQL新功能。窗口函数增强新版本中的窗口函数支持命名窗口(WINDOW)子句,可以利用该子句定义窗口变量,然后在OVER子句... 查看详情

个推解读android13新特性,发布《android13适配指南》(代码片段)

...已经上传到Android开源项目(AOSP)中,Android13正式发布。自从2022年2月Android13第一个预览版上线以来,历经7个月的测试和优化,正式版本的Android13终于来了!Android13仍然聚焦个人隐私保护和安全ÿ 查看详情

个推解读android13新特性,发布《android13适配指南》(代码片段)

...已经上传到Android开源项目(AOSP)中,Android13正式发布。自从2022年2月Android13第一个预览版上线以来,历经7个月的测试和优化,正式版本的Android13终于来了!Android13仍然聚焦个人隐私保护和安全ÿ 查看详情

efcore5新特性`savechangesinterceptor`(代码片段)

EFCore5新特性SaveChangesInterceptorIntro之前EFCore5还没正式发布的时候有发布过一篇关于SaveChangesEvents的文章,有需要看可以移步到efcore新特性SaveChangesEvents,在后面的版本中又加入了Interceptor的支持,可以更方便的实现SaveChanges事件的... 查看详情