ECMAScript 2022 (ES13) is a new JavaScript standard set to be released in June 2022. Let's go through an overview of the last few changes that are to be in the new release as they finished proposals (those that have reached Stage 4 in the proposal process and thus are implemented in several implementations and will be in the next practical revision).
ECMAScript 2022 features nine finished proposals:
- Class field declarations
- Ergonomic brand checks for private fields
- Class Static Block
- Regexp Match Indices
- Await operator at the top-level
- Method .at() function for Indexing
- Accessible Object.prototype.hasOwnProperty()
- Error Cause
- Array find from last
Class field declarations
Before ES13 we would define properties of a class
in its constructor
like this:
class User {
constructor() {
// public field
this.name = "Tom";
// private field
this._lastName = "Brown";
}
getFullName() {
return `${this.name} ${this._lastName}`;
}
}
const user = new User();
user.name;
// "Tom"
user._lastName;
// "Brown"
// no error thrown, we can access it from outside the class
Inside the constructor
, we defined two fields. As you can see one of them is marked with an _
in front of the name which is just a JavaScript
naming convention to declare the field as private
meaning that it can only be accessed from inside of a class
method. But, that's just a naming convention, that's why when we tried to access it, it didn't raise any error.
In ES13 we have an easier way to declare both public
and private
fields. The first thing is that we don't have to define them inside of the constructor
. Secondly, we can also define private
fields by pre-pending #
to their names.
class User {
name = "Tom";
#lastName = "Brown";
getFullName() {
return `${this.name} ${this.#lastName}`;
}
}
const user = new User();
user.name;
// "Tom"
user.getFullName();
// "Tom Brown"
user.#lastName;
// SyntaxError - cannot be accessed or modified from outside the class
The main difference with the previous example is that this time an actual error will be thrown if we try to access or modify the field outside of the class.
Ergonomic brand checks for private fields
This functionality helps us to check that the object has the given private slot in it, for this purpose, the in
operator is used.
class Person {
#name;
constructor(name) {
this.#name = name;
}
static check(obj) {
return #name in obj;
}
}
Person.check(new Person()), // true
Using the same private identifier in different classes. The two classes User
and Person
both have a slot whose identifier is #name
. The in
operator distinguishes them correctly:
class User {
#name;
constructor(name) {
this.#name = name;
}
static check(obj) {
return #name in obj;
}
}
class Person {
#name;
constructor(name) {
this.#name = name;
}
static check(obj) {
return #name in obj;
}
}
User.check(new User()), // true
User.check(new Person()), // false
Person.check(new Person()), // true
Person.check(new User()), // false
Class Static Block
Static initialization blocks in classes. For static data, we have Static fields and Static Blocks that are executed when the class is created. A class
can have any number of static {}
initialization blocks in its class body. These are evaluated, along with any interleaved static field initializers, in the order, they are declared. The super.property
can be used inside a static
block to reference properties of a super class.
class Dictionary {
static words = ["yes", "no", "maybe"];
}
class Words extends Dictionary {
static englishWords = [];
static #localWord = "ok";
// first static block
static {
// link by super to the Dictionary class
const words = super.words;
this.englishWords.push(...words);
}
// second static block
static {
this.englishWords.push(this.#localWord);
}
}
console.log(Words.englishWords);
//Output -> ["yes", "no", "maybe", "ok"]
This example below shows how access can be granted to the private object of a class from an object outside the class
let getClassPrivateField;
class Person {
#privateField;
constructor(value) {
this.#privateField = value;
}
static {
getClassPrivateField = (obj) => obj.#privateField;
}
}
getClassPrivateField(new Person("private value"));
Regexp Match Indices
This upgrade will allow us to use the d
character to specify that we want to get the indices (starting and ending) of the matches of our RegExp. Previously this was not possible. You could only obtain indexing data in the process of string-matching operation.
const fruits = "Fruits: apple, banana, orange";
const regex = /(banana)/g;
const matchObj = regex.exec(fruits);
console.log(matchObj);
// [
// 'banana',
// 'banana',
// index: 15,
// input: 'Fruits: apple, banana, orange',
// groups: undefined
// ]
What we don't know are the indices at which the string ends, something that we can add now the d
character and see the result.
const fruits = "Fruits: apple, banana, orange";
const regex = /(banana)/dg;
const matchObj = regex.exec(fruits);
console.log(matchObj);
// [
// 'banana',
// 'banana',
// index: 15,
// indices:[
// [15, 21],
// [15, 21]
// ]
// input: 'Fruits: apple, banana, orange',
// groups: undefined
// ]
New field indices
as you can see it returned [15,21]
We can use Regexp.exec
or String.matchAll
to find a list of matches, with the main difference between them being that Regexp.exec
returns its results one by one whereas String.matchAll
returns an iterator.
Await operator at the top-level
The await
operator can only be used within an async
method is probably an error you have encountered frequently. In ES13 we will be able to use it outside of the context of an async
method.
Loading modules dynamically
const strings = await import(`./example.mjs`);
Using a fallback if module loading fails
let jQuery;
try {
jQuery = await import("https://cdn-a.com/jQuery");
} catch {
jQuery = await import("https://cdn-b.com/jQuery");
}
Using whichever resource loads fastest
const resource = await Promise.any([
fetch("http://example1.com"),
fetch("http://example2.com"),
]);
Method .at() function for Indexing
Currently, to access a value from the end of an indexable object, the common practice is to write arr[arr.length - N]
, where N is the Nth item from the end (starting at 1). This requires naming the indexable twice and additionally adds 7 more characters for the .length
.
Another method that avoids some of those drawbacks, but has some performance drawbacks of its arr.slice(-N)[0]
const arr = [100, 200, 300, 400];
arr[0]; // 100
arr[arr.length - 2]; // 300
arr.slice(-2)[0]; // 300
We would be able to write:
const arr = [100, 200, 300, 400];
arr.at(0); // 100
arr.at(-2); // 300
const str = "ABCD";
str.at(-1); // 'D'
str.at(0); // 'A'
The following "indexable" types have method .at()
:
string
Array
- All Typed Array classes:
Uint8Array
etc.
Accessible Object.prototype.hasOwnProperty()
In JavaScript, we already have an Object.prototype.hasOwnProperty
but, as the MDN documentation also suggests, it's best to not use hasOwnProperty
outside the prototype itself as it is not a protected property, meaning that an object
could have its property called hasOwnProperty
that has nothing to do with Object.prototype.hasOwnProperty
const person = {
name: "Roman",
hasOwnProperty: () => {
return false;
},
};
person.hasOwnProperty("name"); // false
Another problem Object.create(null)
will create an object that does not inherit from Object.prototype
, making those methods inaccessible.
Object.create(null).hasOwnProperty("name");
// Uncaught TypeError: Object.create(...).hasOwnProperty is not a function
The Object.hasOwn()
method with the same behavior as calling Object.hasOwnProperty
, takes our Object
as the first argument and the property we want to check as the second:
const object = { name: "Mark" };
Object.hasOwn(object, "name"); // true
const object2 = Object.create({ name: "Roman" });
Object.hasOwn(object2, "name"); // false
Object.hasOwn(object2.__proto__, "name"); // true
const object3 = Object.create(null);
Object.hasOwn(object3, "name"); // false
Error Cause
To help unexpected behavior diagnosis, errors need to be augmented with contextual information like error messages, and error instance properties to explain what happened at the time, .cause
property on the error object would allow us to specify which error caused the other error. So errors can be chained without unnecessary and over-elaborate formalities on wrapping the errors in conditions.
try {
apiCallThatCanThrow();
} catch (err) {
throw new Error("New error message", { cause: err });
}
Array find from last
In JavaScript, we already have an Array.prototype.find
and Array.prototype.findIndex
. We know to find from last may have better performance (The target element on the tail of the array, could append with push
or concat
in a queue or stack, eg: recently matched time point in a timeline). If we care about the order of the elements (May have a duplicate item in the array, eg: last odd in the list of numbers), a better way to use new methods Array.prototype.findLast
and Array.prototype.findLastIndex
Instead of writing for find from last:
const array = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }];
// find
[...array].reverse().find((n) => n.value % 2 === 1); // { value: 3 }
// findIndex
array.length - 1 - [...array].reverse().findIndex((n) => n.value % 2 === 1); // 2
array.length - 1 - [...array].reverse().findIndex((n) => n.value === 9); // should be -1, but 4
We would be able to write:
const array = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }];
// find
array.findLast((n) => n.value % 2 === 1); // { value: 3 }
// findIndex
array.findLastIndex((n) => n.value % 2 === 1); // 2
array.findLastIndex((n) => n.value === 9); // -1
Final notes
So we have learned about the last ES13 JavaScript features that would improve your efficiency. These ES13 JavaScript features are all set to be launched in June of 2022. Let's wait for the release.