There are eight new ES2022 features, all currently under stage 4 finalization and expected to become standard by June. All current ECMAScript proposals can be viewed on TC39’s website. Here are the ES2022 proposals.

Class Fields

This is a collection of class-related features. Starting with ES6 Class, you can use the Class keyword in JavaScript to write code in the traditional object-oriented programming paradigm. But the class syntax has always been imperfect, and this proposal is an enrichment of the class syntax.

Private Instance Fields Private Instance field

The fields in the previous instances of the class were accessible, unsafe, and instance tainted. Now we declare private properties with the symbol #. Now when we access private properties through instances, static checks will report an error.

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

Copy the code

Private instance methods and accessors

Similarly, ES2022 also supports private instance methods and accessors, again declared with the symbol #.

class Counter {
  NUM = 1

  // Private accessors
  get #getNum() {
    return this.NUM
  }
  // Non-private accessors
  get getNum() {
    return this.NUM
  }

  // Private instance methods
  #getStr() {
    return 'Private instance methods'
  }
  // Non-private instance methods
  getStr() {
    return 'Non-private instance methods'}}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()) // Non-private instance methods

Copy the code

An error message was reported while trying to access the private accessor #getNum and the private method #getStr().

Class Public Instance Fields Public Instance Fields of the Class

Prior to ES2022, we wanted to define the default value of an instance, which could only be defined by constructor:

class Counter {
  constructor() {
    this.num = 0; }}Copy the code

Now we can go like this:

class Counter {
  num = 0;
}
Copy the code

Static class fields and methods Static class fields and methods

Use the symbol static to declare static fields and methods that belong to an entire class, not a specific instance.

class Counter {
  // Static field
  static NUM = 1
  // Static method
  static getNUM() {
    return this.NUM
  }
}

// The static fields and methods of the class can only be accessed directly through the class
console.log(Counter.NUM) / / 1
console.log(Counter.getNUM()) / / 1

// Static fields and methods of the class cannot be accessed through the instance
const counter = new Counter()
console.log(counter.NUM) // undefined
console.log(counter.getNUM()) // TypeError: counter.getNUM is not a function

Copy the code

Private static fields and methods Static class fields and private static methods

class Counter {
  Static private fields
  static #NUM = 1
  Static public fields
  static NUM = 1

  Static private methods
  static #getNUM() {
    return this.#NUM
  }
  Static public methods
  static getNUM() {
    return this.#NUM
  }
}

// A class can access only static public fields and methods, not static private fields and methods
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

// The instance cannot access static fields or methods, public or private
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

Copy the code

Several of the new features mentioned above about classes are already common in other object-oriented languages.

RegExp Match Indices

The exec() method of the re performs a search match in a specified string, returning either a result array or NULL. Previous regular expressions had the/I, /g, and /m modifiers, with/I for case-insensitive, /g for global matching, and /m for multi-line matching. Now a /d modifier has been added.

Add /d to the result array returned by the exec method with an extra indices property that returns the start and end indexes of the matching results.

const str1 = 'foo bar foo';

const regex1 = new RegExp('foo'.'gd');
// add the /d modifier to the indices property
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');
// Do not add /d modifier
console.log(regex2.exec(str2).indices); // Output: undefined

Copy the code

Top-level await

Before we used to use await, we had to put it in async function. This limits some scenarios, such as when we load imports asynchronously in the global scope, or when we initialize resources. This feature facilitates these scenarios:

// when no top-level await is used
import { process } from "./some-module.mjs";
let output;
// Solve the problem with async and execute functions immediately
(async() = > {const dynamic = await import(computedModuleSpecifier);
  const data = awaitfetch(url); output = process(dynamic.default, data); }) ();export { output };
Copy the code
// With top-level await, there is no need to create additional async functions
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
Copy the code

Ergonomic brand checks for Private Fields

In the new proposal, the in keyword can be used to detect whether an instance’s class contains private fields, private attributes, private functions, and private accessors. Without this method, it is difficult to determine from the instance whether the current class contains private fields.

class Demo {
  #foo

  #method() {}

  get #getter() {}

  // Check if there are private fields
  static hasPrivateFields(obj) {
    return #foo in obj && #method in obj && #getter in obj
  }
}
const obj = new Demo()
console.log(Demo.hasPrivateFields(obj)) //true
Copy the code

.at()

Previously, if we wanted to get the last element of the array, we could only use arr[arr.leng-1], not arr[-1], because the [] syntax applies to all objects. [-1] actually gets the property of the object with the key “-1”, not the last property of the object.

const arr = [1.2.3]

console.log(arr[0]) / / 1
console.log(arr[arr.length - 1]) / / 3
console.log(arr[-1])//undefined
Copy the code

In the new proposal, at() addresses this problem. Now we can use.at() to take the reciprocal element of an array, which is especially useful for dynamic arrays.

const arr = [1.2.3]

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

// Multiply I by 2 and take the last element
console.log(arr.map((i) = > (i = i * 2)).at(-1))/ / 6
Copy the code

Accessible Object.prototype.hasOwnProperty

ES2022 adds the hasOwn() method to the Object prototype, which is very useful for determining whether an attribute is a property of the Object itself or a property in the prototype chain.

// object inherits from the object {foo: 'foo'}
const object = Object.create({ foo: 'foo' })
// Add the bar attribute to itself
object.bar = 'bar'

// Iterate over the properties of the object
for (const key in object) {
  // Get attributes only on the object itself
  if (Object.hasOwn(object, key)) {
    console.log(key) // bar}}// Walk through all the properties of the object, including the properties on the prototype chain
for (const key in object) {
  console.log(key) //bar foo
}
Copy the code

Object. HasOwn () method is better than the Object. The prototype. The hasOwnProperty () method is more convenient and safe strategy. When object.create (null) creates an Object that does not inherit from Object.prototype, the hasOwnProperty method cannot access it.

Class Static Block

Previously, we could only initialize static variables of a class when the class was defined. For variables that could not be declared, we used a function to initialize them. We can’t put them in constructors, because in constructors, we can only initialize variables that are specific to one instance.

We can now create a block of code inside the class that is specifically initialized for static members, which we call a “static block.”

// When not using static code blocks:
class C {
  staticx = ... ;static y;
  static z;
}

try {
  const obj = doSomethingWith(C.x);
  C.y = obj.y
  C.z = obj.z;
}
catch{ C.y = ... ; C.z = ... ; }// When using static code blocks:
class C {
  staticx = ... ;static y;
  static z;
  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = ... ;this.z = ... ; }}}Copy the code

Error Cause

We had to do a lot earlier to add context information to the errors we caught. In addition, there is no consensus on which attribute to use to represent the cause of the error, which prevents developers from accurately understanding the cause of the error.

async function doJob() {
  const rawResource = await fetch('//domain/resource-a')
    .catch(err= > {
      // How do we encapsulate err information?
      // 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
Copy the code

The solution now provided by Error is to add an additional option parameter to the constructor, the cause parameter, which describes the cause of the Error and whose value is assigned as an attribute to the Error instance. As a result, errors can be linked together without having to wrap them in cumbersome forms.

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
Copy the code

That’s all the features of ES2022.

Refer to the link

  • Es6 proposal address
  • Browser support for ES6

If you have any suggestions or questions, please contact us.