Public and private instance fields
Previously, the standard approach when declaring a member field inside the
class keyword was to introduce it in the constructor. The newest ECMAScript specification lets us define the member field inline as part of the class body. As shown in Listing 1, we can use a hashtag to denote a private field.
Listing 1. Inline public and private class fields
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,
artist member is prefixed with a hash (#) symbol, so it is private. We allow for setting these fields in the constructor. Notice 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 the
Song class, setting both fields via the constructor. We then output the fields to the console. The point is that
song1.artist is not visible to the outside world, and outputs undefined.
Note, also, that even
song1.hasOwnProperty("artist") will return false. Additionally, we cannot create private fields on the class later using assignment.
Overall, this is a nice addition, making for cleaner code. 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 it is with private instance fields. So, you could 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;
The class fields proposal also introduces static members. These look and work similarly to how they do in Java: if a member has the
static keyword modifier, it exists on the class instead of object instances. You could add a static member to the
Song class as shown in Listing 3.
Listing 3. Add a static member to a class
class Song static label = "Exile";
The field is then only accessible via the class name,
#label; that is, a private static field.
RegExp match indices
match has been upgraded to include more information about the 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 a deep dive on the meaning of
Basically, using the
/d flag causes the regex engine to include the start and end of all matching substrings. When the flag is present, the
indices property on the
exec 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, the indices will have a member called
groups, whose first dimension contains the name of the group. Consider Listing 4, which is taken from the proposal.
Listing 4. Regex group indices
const re1 = /a+(?<Z>z)?/d; // block 1 const s1 = "xaaaz"; const m1 = re1.exec(s1); m1.indices === 1; m1.indices === 5; s1.slice(...m1.indices) === "aaaz"; // block 2 m1.indices === 4; m1.indices === 5; s1.slice(...m1.indices) === "z"; // block 3 m1.indices.groups["Z"] === 4; m1.indices.groups["Z"] === 5; s1.slice(...m1.indices.groups["Z"]) === "z"; // block 4 const m2 = re1.exec("xaaay"); m2.indices === 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 named matching group (named
Z) that matches the
z char zero or more times.
Code block 1 demonstrates that
m1.indices contain 1 and 5, respectively. That is because the first match for the regex is the string from the first
a to the string ending in
z. Block 2 shows the same thing for the
Block 3 shows accessing the first dimension with the named group via
m1.indices.groups. There is one matched group, the final
z character, and it has a start of 4 and an end of 5.
Finally, block 4 demonstrates that unmatched indices and groups will return undefined.
The bottom line is that if you need access to the details of where groups are matched within a string, you can now use the regex match indices feature to get it.
The ECMAScript specification now includes the ability to package asynchronous modules. When you import a module wrapped in
await, the including module will not execute until all the
awaits are fulfilled. This avoids potential race conditions when dealing with interdependent asynchronous module calls. See the top-level await proposal for details.
Listing 5 includes an example borrowed from the proposal.
Listing 5. Top-level await
// 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);
await keyword in front of its use of dependent modules
data. This means that when
usage.mjs will not be executed until
awaiting.mjs dependencies have finished loading.
Ergonomic brand checks for private fields
As developers, we want code that is comfortable—ergo ergonomic private fields. This new feature lets us check for the existence of a private field on a class without resorting to exception handling.
Listing 6 shows this new, ergonomic way to check for a private field from within a class, using the
Listing 6. Check 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 find yourself needing 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
Object.hasOwn is an improved version of
Object.hasOwnProperty. It works for some edge cases, like when an object is created with
Object.create(null). Note that
hasOwn is a static method—it doesn’t 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
Class static block
static keyword on a block of code that is run when the class is loaded, and it will have access to static members.
Listing 9 has 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”;
Last but not least, the
Error class now incorporates cause support. This allows for Java-like stack traces in error chains. The error constructor now allows for an options object that includes a
cause field, as shown in Listing 10.
Listing 10. Error cause
throw new Error('Error message', cause: errorCause );