Skip to main content

39 posts tagged with "ECMAScript"

View All Tags

Weak references and finalizers

· 10 min read
Sathya Gunasekaran ([@_gsathya](https://twitter.com/_gsathya)), Mathias Bynens ([@mathias](https://twitter.com/mathias)), Shu-yu Guo ([@_shu](https://twitter.com/_shu)), and Leszek Swirski ([@leszekswirski](https://twitter.com/leszekswirski))

Generally, references to objects are strongly held in JavaScript, meaning that as long you have a reference to the object, it won’t be garbage-collected.

const ref = { x: 42, y: 51 };
// As long as you have access to `ref` (or any other reference to the
// same object), the object won’t be garbage-collected.

Currently, WeakMaps and WeakSets are the only way to kind-of-weakly reference an object in JavaScript: adding an object as a key to a WeakMap or WeakSet doesn’t prevent it from being garbage-collected.

const wm = new WeakMap();
{
const ref = {};
const metaData = 'foo';
wm.set(ref, metaData);
wm.get(ref);
// → metaData
}
// We no longer have a reference to `ref` in this block scope, so it
// can be garbage-collected now, even though it’s a key in `wm` to
// which we still have access.

Stable `Array.prototype.sort`

· 3 min read
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Let’s say you have an array of dogs, where each dog has a name and a rating. (If this sounds like a weird example, you should know that there’s a Twitter account that specializes in exactly this… Don’t ask!)

// Note how the array is pre-sorted alphabetically by `name`.
const doggos = [
{ name: 'Abby', rating: 12 },
{ name: 'Bandit', rating: 13 },
{ name: 'Choco', rating: 14 },
{ name: 'Daisy', rating: 12 },
{ name: 'Elmo', rating: 12 },
{ name: 'Falco', rating: 13 },
{ name: 'Ghost', rating: 14 },
];
// Sort the dogs by `rating` in descending order.
// (This updates `doggos` in place.)
doggos.sort((a, b) => b.rating - a.rating);

`Symbol.prototype.description`

· One min read
Mathias Bynens ([@mathias](https://twitter.com/mathias))

JavaScript Symbols can be given a description upon creation:

const symbol = Symbol('foo');
// ^^^^^

Previously, the only way to access this description programmatically was indirectly through Symbol.prototype.toString():

const symbol = Symbol('foo');
// ^^^^^
symbol.toString();
// → 'Symbol(foo)'
// ^^^
symbol.toString().slice(7, -1); // 🤔
// → 'foo'

However, the code is slightly magical-looking, not very self-explanatory, and violates the “express intent, not implementation” principle. The above technique also doesn’t let you distinguish between a symbol with no description (i.e. Symbol()) and a symbol with the empty string as its description (i.e. Symbol('')).

`Object.fromEntries`

· 4 min read
Mathias Bynens ([@mathias](https://twitter.com/mathias)), JavaScript whisperer

Object.fromEntries is a useful addition to the built-in JavaScript library. Before explaining what it does, it helps to understand the pre-existing Object.entries API.

Object.entries

The Object.entries API has been around for a while.

For each key-value pair in an object, Object.entries gives you an array where the first element is the key, and the second element is the value.

Object.entries is especially useful in combination with for-of, as it enables you to very elegantly iterate over all key-value pairs in an object:

const object = { x: 42, y: 50 };
const entries = Object.entries(object);
// → [['x', 42], ['y', 50]]

for (const [key, value] of entries) {
console.log(`The value of ${key} is ${value}.`);
}
// Logs:
// The value of x is 42.
// The value of y is 50.

Unfortunately, there’s no easy way to go from the entries result back to an equivalent object… until now!

Object.fromEntries

The new Object.fromEntries API performs the inverse of Object.entries. This makes it easy to reconstruct an object based on its entries:

const object = { x: 42, y: 50 };
const entries = Object.entries(object);
// → [['x', 42], ['y', 50]]

const result = Object.fromEntries(entries);
// → { x: 42, y: 50 }

One common use case is transforming objects. You can now do this by iterating over its entries, and then using array methods you might already be familiar with:

const object = { x: 42, y: 50, abc: 9001 };
const result = Object.fromEntries(
Object.entries(object)
.filter(([ key, value ]) => key.length === 1)
.map(([ key, value ]) => [ key, value * 2 ])
);
// → { x: 84, y: 100 }

In this example, we’re filtering the object to only get keys of length 1, that is, only the keys x and y, but not the key abc. We then map over the remaining entries and return an updated key-value pair for each. In this example, we double each value by multiplying it by 2. The end result is a new object, with only properties x and y, and the new values.

Promise combinators

· 5 min read
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Since the introduction of promises in ES2015, JavaScript has supported exactly two promise combinators: the static methods Promise.all and Promise.race.

Two new proposals are currently making their way through the standardization process: Promise.allSettled, and Promise.any. With those additions, there’ll be a total of four promise combinators in JavaScript, each enabling different use cases.

`Array.prototype.flat` and `Array.prototype.flatMap`

· 2 min read
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Array.prototype.flat

The array in this example is several levels deep: it contains an array which in turn contains another array.

const array = [1, [2, [3]]];
// ^^^^^^^^^^^^^ outer array
// ^^^^^^^^ inner array
// ^^^ innermost array

Array#flat returns a flattened version of a given array.

array.flat();
// → [1, 2, [3]]

// …is equivalent to:
array.flat(1);
// → [1, 2, [3]]

Numeric separators

· 2 min read
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Large numeric literals are difficult for the human eye to parse quickly, especially when there are lots of repeating digits:

1000000000000
1019436871.42

To improve readability, a new JavaScript language feature enables underscores as separators in numeric literals. So, the above can now be rewritten to group the digits per thousand, for example:

`String.prototype.matchAll`

· 3 min read
Mathias Bynens ([@mathias](https://twitter.com/mathias))

It’s common to repeatedly apply the same regular expression on a string to get all the matches. To some extent, this is already possible today by using the String#match method.

In this example, we find all words that consist of hexadecimal digits only, and then log each match:

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
for (const match of string.match(regex)) {
console.log(match);
}

// Output:
//
// 'DEADBEEF'
// 'CAFE'

However, this only gives you the substrings that match. Usually, you don’t just want the substrings, you also want additional information such as the index of each substring, or the capturing groups within each match.

It’s already possible to achieve this by writing your own loop, and keeping track of the match objects yourself, but it’s a little annoying and not very convenient:

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
let match;
while (match = regex.exec(string)) {
console.log(match);
}

// Output:
//
// [ 'DEADBEEF', index: 19, input: 'Magic hex numbers: DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: 'Magic hex numbers: DEADBEEF CAFE' ]

The new String#matchAll API makes this easier than ever before: you can now write a simple for-of loop to get all the match objects.

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
for (const match of string.matchAll(regex)) {
console.log(match);
}

// Output:
//
// [ 'DEADBEEF', index: 19, input: 'Magic hex numbers: DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: 'Magic hex numbers: DEADBEEF CAFE' ]

String#matchAll is especially useful for regular expressions with capture groups. It gives you the full information for each individual match, including capturing groups.

const string = 'Favorite GitHub repos: tc39/ecma262 v8/v8.dev';
const regex = /\b(?<owner>[a-z0-9]+)\/(?<repo>[a-z0-9\.]+)\b/g;
for (const match of string.matchAll(regex)) {
console.log(`${match[0]} at ${match.index} with '${match.input}'`);
console.log(`→ owner: ${match.groups.owner}`);
console.log(`→ repo: ${match.groups.repo}`);
}

Public and private class fields

· 4 min read
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Several proposals expand the existing JavaScript class syntax with new functionality. This article explains the new public class fields syntax in V8 v7.2 and Chrome 72, as well as the upcoming private class fields syntax.

Here’s a code example that creates an instance of a class named IncreasingCounter:

const counter = new IncreasingCounter();
counter.value;
// logs 'Getting the current value!'
// → 0
counter.increment();
counter.value;
// logs 'Getting the current value!'
// → 1

Note that accessing the value executes some code (i.e., it logs a message) before returning the result. Now ask yourself, how would you implement this class in JavaScript? 🤔

ES2015 class syntax

Here’s how IncreasingCounter could be implemented using ES2015 class syntax:

class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
console.log('Getting the current value!');
return this._count;
}
increment() {
this._count++;
}
}

The class installs the value getter and an increment method on the prototype. More interestingly, the class has a constructor that creates an instance property _count and sets its default value to 0. We currently tend to use the underscore prefix to denote that _count should not be used directly by consumers of the class, but that’s just a convention; it’s not really a “private” property with special semantics enforced by the language.