ES2022 新特性必知必会

ES2022 新特性共有8条,当前全部处于 stage 4 定案阶段,预计将在今年六月成为标准。ECMAScript 当前的全部提案,可以在 TC39 的官网查看。下面介绍下ES2022的提案。

Class Fields

这是一个与相关特性的集合。从 ES6 Class 开始,我们就可以在 JavaScript 中使用 class 关键字书写传统面向对象编程范式的代码了。但的语法一直不太完善,这个提案就是对语法 的一个丰富。

Private Instance Fields 私有实例字段

之前类的实例中的字段都是可以被访问的,不安全且存在实例污染。现在我们通过符号#来声明私有属性。现在当我们通过实例访问私有属性时,静态检查时直接报错。

class Counter {
  #NUM = 0
  NUM = 1
}

const counter = new Counter()
console.log(counter.#NUM) // VM723:1 Uncaught SyntaxError: Private field '#NUM' must be declared in an enclosing class
console.log(counter.NUM) // 1

复制代码

Private instance methods and accessors 私有实例方法和访问器

同样的,ES2022也支持私有实例方法和访问器,也是使用符号#来声明。

class Counter {
  NUM = 1

  //私有访问器
  get #getNum() {
    return this.NUM
  }
  //非私有访问器
  get getNum() {
    return this.NUM
  }

  //私有实例方法
  #getStr() {
    return '私有实例方法'
  }
  //非私有实例方法
  getStr() {
    return '非私有实例方法'
  }
}

const counter = new Counter()
console.log(counter.#getNum) // Private field '#getNum' must be declared in an enclosing class
console.log(counter.getNum) // 1
console.log(counter.#getStr()) // Private field '#getStr' must be declared in an enclosing class
console.log(counter.getStr()) //非私有实例方法

复制代码

在尝试访问私有访问器#getNum和私有方法#getStr()时,发生报错提示。

Class Public Instance Fields 类公共实例字段

在 ES2022 之前,我们想定义一个实例的默认值,只能通过 constructor 定义:

class Counter {
  constructor() {
    this.num = 0;
  }
}
复制代码

现在我们可以这样:

class Counter {
  num = 0;
}
复制代码

Static class fields and methods 静态类字段和方法

使用符号static来声明静态字段和方法,静态类字段和方法属于整个类,并非某一具体的实例。

class Counter {
  // 静态字段
  static NUM = 1
  // 静态方法
  static getNUM() {
    return this.NUM
  }
}

//只能通过类来直接访问类的静态字段和静态方法
console.log(Counter.NUM) //1
console.log(Counter.getNUM()) // 1

// 无法通过实例访问类的静态字段和静态方法
const counter = new Counter()
console.log(counter.NUM) // undefined
console.log(counter.getNUM()) // TypeError: counter.getNUM is not a function

复制代码

private static fields and methods 静态类字段和私有静态方法

class Counter {
  // 静态私有字段
  static #NUM = 1
  // 静态公有字段
  static NUM = 1

  // 静态私有方法
  static #getNUM() {
    return this.#NUM
  }
  // 静态公有方法
  static getNUM() {
    return this.#NUM
  }
}

//类只能访问静态公有字段和静态公有方法,无法访问静态私有字段和静态私有方法
console.log(Counter.NUM) //1
console.log(Counter.#NUM) //Private field '#NUM' must be declared in an enclosing class
console.log(Counter.getNUM()) // 1
console.log(Counter.#getNUM()) // Private field '#getNUM' must be declared in an enclosing class

// 实例无法访问静态字段和静态方法,公有私有都不行
const counter = new Counter()
console.log(counter.NUM) // undefined
console.log(counter.#NUM) // Private field '#NUM' must be declared in an enclosing class
console.log(counter.getNUM()) // TypeError: counter.getNUM is not a function
console.log(counter.#getNUM()) // TypeError: counter.getNUM is not a function

复制代码

上述提到关于类的几个新特性,在其他面向对象的语言中已经十分常见。

RegExp Match Indices

正则的exec()方法是在一个指定字符串中执行一个搜索匹配,会返回一个结果数组或 null。以前的正则表达式有 i 和 g 两个修饰符,i 代表忽略大小写,g 代表全局匹配,现在又加了一个 d修饰符。

加上d 后,exec方法返回的结果数组就会多出一个 indices属性,用来返回匹配结果的开始索引和结束索引

const str1 = 'foo bar foo';

const regex1 = new RegExp('foo', 'gd');
//加上/d修饰符,存在indices属性
console.log(regex1.exec(str1).indices[0]); // Output: Array [0, 3]
console.log(regex1.exec(str1).indices[0]); // Output: Array [8, 11]


const str2 = 'foo bar foo';

const regex2 = new RegExp('foo');
//没加/d修饰符,不存在indices属性
console.log(regex2.exec(str2).indices); // Output: undefined

复制代码

Top-level await

以前我们使用 await 时,必须要放到 async 函数里。这就限制了一些场景,比如我们在全局作用域使用 import 的异步加载方式,亦或者初始化资源的时候。而这个特性就是为这些场景提供了便利:

// 不使用顶层await时
import { process } from "./some-module.mjs";
let output;
// 通过async和立即执行函数,解决该问题
(async () => {
  const dynamic = await import(computedModuleSpecifier);
  const data = await fetch(url);
  output = process(dynamic.default, data);
})();
export { output };
复制代码
// 使用顶层await时,不再需要额外创建async函数
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
复制代码

Ergonomic brand checks for Private Fields

在新的提案中,可以使用in关键字用来检测某一实例的类中是否含有私有字段,私有字段包含私有属性,私有函数以及私有访问器。没有该方法,很难从实例中判断当前类中是否含有私有字段。

class Demo {
  #foo

  #method() {}

  get #getter() {}

  //检测是否含有私有字段
  static hasPrivateFields(obj) {
    return #foo in obj && #method in obj && #getter in obj
  }
}
const obj = new Demo()
console.log(Demo.hasPrivateFields(obj)) //true
复制代码

.at()

在之前,我们如果想获取数组的最后一个元素,只能使用arr[arr.length-1],无法使用arr[-1],因为[]语法适用于所有对象,[-1]实际上是获取具有键为“-1”的对象的属性,而非对象的最后一个属性。

const arr = [1, 2, 3]

console.log(arr[0]) // 1
console.log(arr[arr.length - 1]) // 3
console.log(arr[-1])//undefined
复制代码

在新的提案中,at()解决了该问题。现在我们可以使用.at()取数组的倒数元素,这对动态数组尤其好用。

const arr = [1, 2, 3]

console.log(arr[0]) // 1
console.log(arr[arr.length - 1]) // 3
console.log(arr.at(-1)) // 3

//i乘以2后,取最后一个元素
console.log(arr.map((i) => (i = i * 2)).at(-1))// 6
复制代码

Accessible Object.prototype.hasOwnProperty

ES2022在Object原型上新增了hasOwn()方法,使用这个方法能够判断某个属性是对象本身的属性,还是原型链上的属性,非常好用。

// object继承自对象{ foo: 'foo' }
const object = Object.create({ foo: 'foo' })
// 再给本身添加属性bar
object.bar = 'bar'

// 遍历object对象上的属性
for (const key in object) {
  //只获取对象本身上的属性
  if (Object.hasOwn(object, key)) {
    console.log(key) // bar
  }
}
// 遍历object对象上的所有属性,含原型链上的属性
for (const key in object) {
  console.log(key) //bar foo
}
复制代码

Class Static Block

以前,我们初始化类的静态变量只能在定义类的时候去做,而对于一些无法显示声明的变量,我们往往使用一个函数去初始化。我们也不能放到构造函数里面,因为在构造函数中,只能初始化具体某一实例的变量。

现在,我们可以在类内部开辟一个专门为静态成员初始化的代码块,我们称之为“静态代码块”

// 不使用静态代码块的时候:
class C {
  static x = ...;
  static y;
  static z;
}

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

// 使用静态代码块的时候:
class C {
  static x = ...;
  static y;
  static z;
  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = ...;
      this.z = ...;
    }
  }
}
复制代码

Error Cause

在之前我们必须做很多才能为捕获的错误增加上下文信息。此外,对于使用哪个属性表示错误原因,没有达成共识,这就使得开发人员无法准确的了解错误原因。

async function doJob() {
  const rawResource = await fetch('//domain/resource-a')
    .catch(err => {
      // 我们如何封装err信息?
      // 1. throw new Error('Download raw resource failed: ' + err.message);
      // 2. const wrapErr = new Error('Download raw resource failed');
      //    wrapErr.cause = err;
      //    throw wrapErr;
      // 3. class CustomError extends Error {
      //      constructor(msg, cause) {
      //        super(msg);
      //        this.cause = cause;
      //      }
      //    }
      //    throw new CustomError('Download raw resource failed', err);
    })
  const jobResult = doComputationalHeavyJob(rawResource);
  await fetch('//domain/upload', { method: 'POST', body: jobResult });
}

await doJob(); // => TypeError: Failed to fetch
复制代码

现在 Error 提供的解决方案是向构造函数中添加一个附加选项参数causecause参数用于描述错误原因,其值将作为属性分配给错误实例。因此,可以将错误链接起来,而不必再繁琐的包装错误的形式。

async function doJob() {
  const rawResource = await fetch('//domain/resource-a')
    .catch(err => {
      throw new Error('Download raw resource failed', { cause: err });
    });
  const jobResult = doComputationalHeavyJob(rawResource);
  await fetch('//domain/upload', { method: 'POST', body: jobResult })
    .catch(err => {
      throw new Error('Upload job result failed', { cause: err });
    });
}

try {
  await doJob();
} catch (e) {
  console.log(e);
  console.log('Caused by', e.cause);
}
// Error: Upload job result failed
// Caused by TypeError: Failed to fetch
复制代码

以上就是ES2022的全部特性了。

参考链接

如有建议和问题,欢迎交流。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享