All the new features in ECMAScript 2023 (ES14)

JavaScript continues to evolve, with a nice batch of new features added in ECMAScript 14. Here's what's new for JavaScript developers this year.

birthday cake candles
cbaquiran (CC0)

The wheel of time has carved out another year, and with that comes a new official release of JavaScript: ECMAScript 2023, aka ECMAScript 14. Improvements this year include additions to arrays and support for shebang in ECMAScript files, as well as the expansion of symbol keys for weak collections. The changes are largely refinements as opposed to anything dramatic. Nevertheless, the compound effect is to continue evolving the language. Here’s a look at what’s new in JavaScript in 2023.

Understanding the spec

Before we dig into the details of the new version, it’s worth talking about the ECMAScript specification. You can always find the latest version of the ECMAScript spec hosted at https://tc39.es/ecma262/. If you want to look at the specification in a specific year, you can append that to the URL. For example, this year’s spec is hosted at https://tc39.es/ecma262/2023/

To find what’s new in a recent release, you can scan the intro (for example, anchored here). This gives you a breakdown of what shipped with each new release. You can find out details about a given feature by searching for it. (Maybe in a future version, we’ll have a link to each feature!) 

The ECMAScript specification is an impressive document that serves as both the basic reference for developers and educators as well as the official technical specification for JavaScript engine implementors. This is quite a balancing act, which the spec manages well. It can be a bit cumbersome as a user's guide to the language because of how much information it holds.

Another thing to know about the spec is that it is really a living document that grows interactively as the language is used in the wild. Oftentimes, a new feature is added to the official specification after it has been informally accepted by the user community. We can see this with the shebang syntax this year, for example. Once a feature has been codified and standardized by the specification, the spec serves as a new stable basis for further innovation of that feature. 

Sometimes, the ECMAScript specification introduces groundbreaking ideas. An example is the adoption of the async/await syntax, which was influenced by C#. 

As a language, JavaScript has evolved by leaps and bounds from the days of cut-and-paste rollover effects. The ECMAScript specification process has played a huge role in that evolution. 

Now, let's take a look at the new features introduced to JavaScript in 2023.

Array.prototype.toSorted

Let's begin with the new array method, toSorted().

toSorted has the same signature as Array.prototype.sort(), but it creates a new array instead of operating on the array itself. Here's the new array method in Listing 1.

Listing 1. sort() versus toSorted()


let arr = [5,4,2,3,1]
arr === arr.sort(); // true - [1, 2, 3, 4, 5]

arr === arr.toSorted(); // false - [1, 2, 3, 4, 5]

toSorted(), like sort(), also accepts a single optional argument, a comparator function. For example, we could use toSorted() to create a new array in descending order, as in Listing 2.

Listing 2. Using the comparator function


const numbers = [10, 5, 2, 7, 3, 9, 1, 6, 4]; 
const sortedNumbers = numbers.toSorted((a, b) => { 
  return b - a; 
}); 
console.log(sortedNumbers); // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Let us also make note that toSorted() can be applied to arrays of objects. In that case, you must provide a comparator function that uses data on the objects, since there’s no natural ordering for objects. You can see an example in Listing 3.

Listing 3. toSorted() with objects


// Comparing objects
const objects = [{ name: "John", age: 30 }, { name: "Jane", age: 25 }, { name: "Bill", age: 40 }, { name: "Mary", age: 20 }];
const sortedObjects = objects.toSorted((a, b) => {
  return a.name.localeCompare(b.name); 
});
console.log(sortedObjects);
//[{"name":"Bill","age":40},{"name":"Jane","age":25},{"name":"John","age":30},{"name":"Mary","age":20}]

Listing 3 sorts the objects by the name field.

Array.prototype.toReversed

Like toSorted() and sort(), toReversed() is the duplicating cousin of reverse(). Listing 4 has a couple of quick examples of using toReversed(), including applying it to objects with a comparator function.

Listing 4. toReversed()


["a","b","c","d","e"].toReversed(); // ['e', 'd', 'c', 'b', 'a']

Array.prototype.with

The new with() method lets you modify a single element based on its index, and get back a new array. So, if you know the index and the new value, this method makes it very easy. Note that with() is the copying companion to set(). Listing 5 gives a simple example.

Listing 5. Replacing an element with with()


const arr4 = ["I", "am", "the", "Walrus"];

// Replace the string "Walrus" with "Octopus".
const newArr4 = arr4.with(3, "Ape Man");

console.log(newArr4);

Array.prototype.findLast

The findLast() method lets you get the final instance of a matching element from an array. If no matching element is found, it returns undefined. A simple example is given in Listing 6, where we get the last even number from an array. 

Listing 6. Find the last even number with findLast()


const arr = [54, 34, 55, 75, 98, 77];

const lastEvenIndex = arr.findLast((element) => {
  return element % 2 === 0;
});

console.log(lastEvenIndex); // 98

findLast() also supports passing in a “thisArg” to set the context. That is to say, the second argument will tell the first argument function what the this keyword will refer to. You can see this in action in Listing 7, where we use a custom object to find the first element evenly divisible by 5.

Listing 7. Using the thisArg


const arr6 = [54, 34, 55, 75, 98, 77];
const myObject = {testCase: 5};
const lastEvenIndex = arr5.findLast((element) => {
  return element % myObject.testCase === 0;
}, myObject);

console.log(lastEvenIndex); // 75

Array.prototype.findLastIndex

findLastIndex() works exactly like findLast(), except it gives you the index of the element matching instead of the element itself. For example, Listing 8 shows how to find the index of the last element evenly divisible by 6.

Listing 8. Find the index of element with findLastIndex()


const arr = [54, 34, 55, 75, 98, 77];
arr.findLastIndex(x => x % 6 === 0); // 0

Array.prototype.toSpliced

So far, all the methods we’ve described also apply to TypedArray. The final new array method, toSpliced(), only exists on Array. The toSpliced() method is the copying version of splice()—that familiar old swiss army knife of JavaScript array manipulation. 

Let’s say we have an array of  colors and we need to insert a couple of new ones (pink and cyan) in the middle. You can see this in Listing 9. Remember, this creates a new array rather than modifying the original.

Listing 9. toSpliced() in action


const arr = ["red", "orange", "yellow", "green", "blue", "purple"]; const newArr = arr.toSpliced(2, 1, "pink", "cyan"); console.log(newArr); 
// ["red", "orange", "pink", "cyan", "green", "blue", "purple"]
console.log(newArr[3]);
// 'cyan'
console.log(arr[3]);
// ‘green’

Official shebang support

A shebang is old-school Unix talk for a hashtag followed by an exclamation point: #! (where "bang" is slang for "!"). Since time immemorial, a comment at the head of a file that starts with #! tells the shell that what you’ve got here is an executable script, and what engine to use to run it. For example, Listing 10 is a typical bash script.

Listing 10. Shebang on bash script: hello.sh


#!/bin/bash

echo "Hello, world!"

You can run a file like the one in Listing 10 directly with ./hello.sh.

You can do similar with JavaScript, as shown in Listing 11.

Listing 11. Shebang in JS: hello.js


#!/usr/bin/env node

console.log("Hello, world!");

Listing 11 tells the operating system to use the node program to run this script. Now, you can just type ./hello.js to run it. Without the #!, that wouldn't work.

Shebang support is one of those feature updates in the spec that has already been implemented and unofficially adopted in multiple contexts. 

Symbols as keys on weak collections

The final new feature in ECMAScript 14 is the expansion of what can be used as keys in weak collections.

Weak collections are a bit esoteric compared to everyday JavaScript usage. In programming, a weak reference is one that will be disposed of if it would otherwise be garbage collected. Put another way, a weak reference by itself is not enough to keep the reference target from being disposed of by the garbage collection algorithm (that's why it's a weak reference).

You can learn more about weak references and when they are handy here. There’s also a good discussion here.

What ES14 does is allow the use of most Symbols as keys in a collection, whereas before you could only use an object. If you wonder what a Symbol is, you are not alone. Here is where you can learn more.

The new feature essentially makes it easier to use weak references in collections like WeakMaps by easing the restrictions on what can be used for keys. A simple example is shown in Listing 12.

Listing 12. Using a symbol as a key on WeakMap


var map = new WeakMap(); // create a weak map
function useSymbol(symbol){
    doSomethingWith(symbol);
    var called = map.get(symbol) || 0;
    called++; // called one more time
    if(called > 2) console.log(“Called more than twice”);
    map.set(symbol, called);
}

let mySymbol = Symbol(“FooBar”);
useSymbol(mySymbol);
useSymbol(mySymbol);
useSymbol(mySymbol);

delete mySymbol; // No live references are left to mySymbol, so we can count on the garbage collector eliminating the entry in the weakMap when it runs (eventually)

Listing 12 is modified from the above linked StackOverflow answer. In this example, the purpose is to allow for calling the counter from an external caller and to dispose of the map entry when there are no references left. The code itself has no way of knowing when the reference is no longer needed, and if you use a normal Map, you will get a memory leak. This is because the code will hold onto the reference even after the client calling it no longer needs it. In this case, where we use the WeakMap, we can count on the garbage collection dropping the map entry when no more references exist to the key symbol.

Conclusion

Although 2023 was a relatively quiet year for JavaScript, ECMAScript 14 added some useful features and kept the official spec up to speed with the real world. Coming in the next version, we’ll have a slew of changes including a shiny new Temporal API for handling datetimes.

Copyright © 2023 IDG Communications, Inc.