ECMA 2021-2023 new features round-up: new operators, array methods and more

ECMA 2021-2023 new features round-up: new operators, array methods and more

18 Oct 2023 ecma-2021 ecma-2022 ecma-2023 operators

Here's a round-up of some features that have landed in JavaScript in the last few years, thanks to ECMAScript editions 12 onwards, that you may be unaware of.

12th edition (2021) 🔗

New assignment operators 🔗

  • ||= - assigns only if the left side is falsy
  • &&= assigns only if the left side is truthy
  • ??= assigns only if the left side is nullish (i.e. null or undefined)

So for example:

let foo; foo ||= 'bar'; console.log(foo); //"bar"

The variable is created but has a value of undefined. Since undefined equates to falsy, our update to the variable succeeds.

Of course, there's various ways to do this in JavaScript. Assuming let foo;, all of the following are equivalent:

if (!foo) foo = 'bar'; //or foo = foo || 'bar'; //or foo = foo ? foo : 'bar';

Promise.any() 🔗

See my dedicated article on Promise.any().

String.prototype.replaceAll() 🔗

This method works like its replace() counterpart except that, if you pass it a string not a RegExp, it replaces all instances not just the first.

'aba'.replace('a', '*'); //"*ba" 'aba'.replaceAll('a', '*'); //"*b*"

Because the method is intended solely for global replacement, if you pass a RegExp to it then you must use the global g flag, otherwise it'll raise an error. (For this reason it makes little sense to use this method with a RegExp - you might as well stick with replace() and save three bytes!)

'aba'.replace(/a/g, '*'); //"*b*" 'aba'.replace(/a/, '*'); //error

Numeric literal separator 🔗

You can now format numeric literals with underscore separators for easier reading.

2_000 + 5; //2005

The underscore can appear anywhere in the number (except at the start or end) - it's purely syntax sugar and does not affect numerical operation:

3_901_1433 === 39011433 //true

Since it's purely a visual thing, you can even use multiple underscores:

1_000_000

Here's some more info on this feature.

13th edition (2022) 🔗

Top-level await 🔗

Normally you can use the await keyword only in an asynchronous function. Now, though, you can use await in the top level of your code, provided you're in a module.

Check out my in-depth guide to async/await

Private class members 🔗

This one was a long time coming and is present in just about all other languages. We've had workarounds for years (mostly using constructors), but finally there's an official implementation for private properties, methods, statics etc. in your classes, just by prefixing them with #.

class Foo { #bar = 'secret!'; getBar() { return this.#bar; } } const inst = new Foo(); inst.getBar(); //"secret!"

Private members are accessible only from within the body of the class itself, not outside, and not even within any sub-classes that extend the class.

inst.#bar; //syntax error - can't access private member

It's important to note that private members are exempt from prototypal inheritance, since they cannot be inherited and are accessible only within the class to which they belong.

[string|array].prototype.at() 🔗

This new method for both strings and arrays is simply a means of grabbing a specific character (strings) or member (arrays) denoted by a passed index.

'foo'.at(1); //"o" ['f', 'o', 'o'].at(0); //"f"

This is equivalent to:

'foo'.substr(1, 1); //or simply 'foo'[1] ['f', 'o', 'o'][0];

Object.hasOwn() 🔗

This is intended as a replacement for Object.prototype.hasOwnProperty, because it works on null-prototype objects i.e. those created via Object.create(null).

It works in the same way, except the first argument is the object to consider.

const myObj = {foo: 'bar'}; Object.hasOwn(myObj, 'foo'); //true myObj.hasOwnProperty('foo'); //equivalent

14th edition (2023) 🔗

Array copy methods 🔗

First up we got a bunch of array methods which - and I don't know who was asking for this - copy an array while performing a mutation on it, leaving the original unchanged.

The new methods are:

  • toSorted() - like sort()
  • toReversed() - like reverse()
  • with() - update member at specific index

So for example:

const arr = [2, 3, 1]; const arr2 = arr.toSorted(); //[1, 2, 3] const arr3 = arr.toReversed(); //[1, 3, 2] const arr4 = arr.with(0, -2); //[-2, 3, 1]

And to prove that the derived arrays are new arrays, not mutated versions of the original:

arr === arr2 || arr === arr3 || arr === arr4; //false

New array find last methods 🔗

Finally, there's a few new methods to find the last value (or index of a value) in an array:

  • findLast() - equivalent of find()
  • findLastIndex() - equivalent of findIndex()

They work the same in that they take a callback function, but these methods count backwards from the end of the array and quit when they find a match.

const arr = [1, 2, 3]; arr.find(val => val !== 2); //1 arr.findLast(val => val !== 2); //3

And that's it! I hope you found this useful.

Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!