8 New JavaScript Features to Use Today

0

ECMAScript 2022 (ES13) was dropped on June 22, codifying the latest batch of new features for JavaScript. Each technology specification is a milestone in a continuous dance with real-world use. As developers use JavaScript, we continually discover opportunities and push the language into new territory. The ECMAScript specification responds by formalizing new functionality. These, in turn, establish a new foundation for the continued evolution of JavaScript.

The ES13 specification brings eight new features to JavaScript. Let’s start with these new features that you can use today.

Class fields

Class fields are an all-encompassing proposition that encompasses several enhancements for managing members on JavaScript classes: public and private class instance fields, private instance methods and accessors, and static class features.

Public and private instance fields

Previously the standard approach when declaring a member field inside the class keyword was to introduce it into the constructor. The latest ECMAScript specification allows us to define the member field inline as part of the class body. As shown in Listing 1, we can use a hashtag to designate a private field.

List 1. Public and private class fields online


class Song {
    title = "";
    #artist = "";
    constructor(title, artist){
      this.title = title;
      this.#artist = artist;
    }
}
let song1 = new Song("Only a Song", "Van Morrison");
console.log(song1.title);
// outputs “Only a Song”
console.log(song1.artist);
// outputs undefined

In Listing 1, we define a class, Songusing the class keyword. This class has two members, title and artist. The artist The member is preceded by a pound sign (#), so it is private. We allow these fields to be set in the constructor. Note that the constructor must access this.#artist with the hash prefix again; otherwise it would overwrite the field with a public member.

Next, we define an instance of Song class, setting both fields through the constructor. We then output the fields to the console. The fact is that song1.artist is not visible to the outside world, and the outputs are undefined.

Also note that even song1.hasOwnProperty("artist") will return false. Also, we cannot create private fields on the class later using assignment.

All in all a nice addition, which makes the code cleaner. Most browsers have supported public and private instance fields for a while and it’s nice to see them officially incorporated.

Private instance methods and accessors

The hash symbol also works as a prefix on methods and accessors. The effect on visibility is exactly the same as with private instance fields. So you can add a private setter and a public getter to the Song.artist field, as shown in Listing 2.

Listing 2. Private instance methods and accessors


class Song {
  title = "";
  #artist = "";
  constructor(title, artist){
    this.title = title;
    this.#artist = artist;
  }
  get getArtist() {
    return this.#artist;
  }
  set #setArtist(artist) {
    this.#artist = artist;
  }
}

Static members

The class fields proposal also introduces static members. These look and work the same as in Java: if a member has the static keyword modifier, it exists on class instead of object instances. You can add a static member to the Song class as shown in Listing 3.

Listing 3. Adding a static member to a class


class Song {
  static label = "Exile";
}

The field is then only accessible via the class name, Song.label. Unlike Java, JavaScript instances do not contain a reference to the shared static variable. Note that it is possible to have a static private field with #label; i.e. a private static field.

RegExp match indices

Regex match has been updated to include more information about matching groups. For performance reasons, this information is only included if the /d flag is added to the regular expression. (See the RegExp Match Indices for ECMAScript proposal for an in-depth analysis of the meaning of /d regex.)

Basically, using the /d flag forces the regex engine to include the start and end of all matching substrings. When the flag is present, the indices property on the exec the results will contain a two-dimensional array, where the first dimension represents the match and the second dimension represents the start and end of the match.

In the case of named groups, indices will have a member called groups, whose first dimension contains the name of the group. Consider List 4, which is taken from the proposition.

Listing 4. Regex group indices


const re1 = /a+(?z)?/d;

// block 1
const s1 = "xaaaz";
const m1 = re1.exec(s1);
m1.indices[0][0] === 1;
m1.indices[0][1] === 5;
s1.slice(...m1.indices[0]) === "aaaz";

// block 2
m1.indices[1][0] === 4;
m1.indices[1][1] === 5;
s1.slice(...m1.indices[1]) === "z";

// block 3
m1.indices.groups["Z"][0] === 4;
m1.indices.groups["Z"][1] === 5;
s1.slice(...m1.indices.groups["Z"]) === "z";

// block 4
const m2 = re1.exec("xaaay");
m2.indices[1] === undefined;
m2.indices.groups["Z"] === undefined;

In Listing 4, we create a regular expression that matches the a char one or more times, followed by a matching named group (named Z) that corresponds to z char zero or more times.

Code block 1 demonstrates that m1.indices[0][0] and m1.indices[0][1] contain 1 and 5 respectively. This is because the first match for the regular expression is the string of the first a to the string ending with z. Block 2 shows the same for the z character.

Block 3 shows how to access the first dimension with the group named via m1.indices.groups. There is a matched group, the final z character, and it has a start of 4 and an end of 5.

Finally, block 4 shows that unmatched indexes and groups will return undefined.

The bottom line is that if you need access to the details of where groups are matched in a string, you can now use the regex match index function to get it.

High level expectation

The ECMAScript specification now includes the ability to package asynchronous modules. When you import a module wrapped in awaitthe included module will not run until all awaits are fulfilled. This avoids potential race conditions when processing interdependent asynchronous module calls. See the top-level wait proposal for more details.

Listing 5 includes an example taken from the proposal.

Listing 5. High Level Expectation


// awaiting.mjs
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
// usage.mjs
import { output } from "./awaiting.mjs";
export function outputPlusValue(value) { return output + value }

console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);

Reviews in awaiting.mjs the await keyword in front of its use of dependent modules dynamic and data. This means that when usage.mjs imports awaiting.mjs, usage.mjs will not be executed before awaiting.mjs the dependencies have finished loading.

Ergonomic brand controls for private fields

As developers, we want comfortable code, that is, user-friendly private fields. This new feature allows us to check the existence of a private field on a class without resorting to exception handling.

Listing 6 shows this new ergonomic way of searching for a private field from a class, using the in keyword.

Listing 6. Checking for the existence of a private field


class Song { 
  #artist; 
  checkField(){ 
    return #artist in this;
  } 
}
let foo = new Song();
foo.checkField(); // true

Listing 6 is contrived, but the idea is clear. When you need to check a class for a private field, you can use the format: #fieldName in object.

Negative indexing with .at()

Gone are the days of arr[arr.length -2]. The .at method on built-in indexables now supports negative indices, as shown in Listing 7.

Listing 7. Negative index with .at()


let foo = [1,2,3,4,5];
foo.at(3); // == 3
hasOwn

possesses

Object.hasOwn is an improved version of Object.hasOwnProperty. This works for some edge cases, like when an object is created with Object.create(null). Note that hasOwn is a static method — it does not exist on instances.

Listing 8. hasOwn() in action


let foo = Object.create(null);
foo.hasOwnProperty = function(){};
Object.hasOwnProperty(foo, 'hasOwnProperty'); // Error: Cannot convert object to primitive value
Object.hasOwn(foo, 'hasOwnProperty'); // true

Listing 8 shows that you can use Object.hasOwn on the foo instance created with Object.create(null).

Class static block

Here is a chance for Java developers to say, Oh, we’ve had this since the 90s. ES 2022 introduces static initialization blocks in JavaScript. Basically you can use the static keyword on a block of code that is executed when the class is loaded, and it will have access to static members.

Listing 9 shows a simple example of using a static block to initialize a static value.

Listing 9. Static Block


class Foo {
  static bar;
  static {
    this.bar = “test”;
  }
}

Cause of error

Last but not least, the Error the class now includes cause support. This allows for Java-like stack traces in error strings. The error constructor now allows an options object that includes a cause field, as shown in Listing 10.

Listing 10. Cause of error


throw new Error('Error message', { cause: errorCause });

Copyright © 2022 IDG Communications, Inc.

Share.

About Author

Comments are closed.