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, Song
using 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 await
the included module will not run until all await
s 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.