Another year, another round of updates for ECMAScript, better known to the world as Javascript. This process of continuous improvement in the language has been ongoing since 2015, and is a lot like getting presents at Christmas from various relatives – sometimes it’s video games and shiny bicycles, other times its sweaters and fruitcake. This year the newest features of Javascript are somewhat in the middle – nothing to get especially wild about but useful nonetheless.
The .at() Method
Way back in primordial times, Javascript developed an array notation for accessing individual positioned elements of an array, the infamous square bracket notation.
let arr = ["a","b","c","d","e"]
arr[3]
=> "d" // Javascript arrays are zero based
While this is undoubtedly useful, it doesn’t always fit into the dot chain notation so beloved by Javascript developers, The .at()
notation fills that gap
arr.at(2)
=> "c"
Additionally, .at()
has been defined such that if you give it a negative value it will return the same thing as trying to do the same thing with list lengths:
arr[arr.length - 1]
=>"e"
arr.at(-1)
=>"e"
It also works on strings (so you don’t need to convert a string into an array) and on typed arrays such as UInt8Array, and I believe it should work on iterators in general. at() is not earth-shaking, but it can make your code more legible.
Regex Match Indexes and the .d Flag
Regular Expressions have been given a fairly significant makeover in recent years within Javascript (to a point where I’d contend it’s beginning to approach Perl as a RegExp superpower), but the new indexes built into the latest version of the RegExp() object have been a long time coming.
Sometimes when you have a regular expression, it is helpful to know where in a block of text a given numbered or named expression match starts and ends. For instance, suppose that you have a regex that looks for basic semantic queries of the form:
let regex = /find (.*?) where (.*?) = (.*)/di
The /d flag here indicates that the regex should generate indices. So when this regex is executed, the returned object identifies the full matching string and the three numbered submatches
let matchObjs = regex.exec("Find People where name = Jane Doe")
matchObjs.map((item,pos)=> console.log(pos,item))
[0: "Find People where name = Jane Doe"
1: "People"
2: "name"
3: "Jane Doe"]
What also gets generated is the indices where these matching terms were located, helpfully named indices.
matchObjs.indices
[0: [0, 33]
1: [5, 11]
2: [18, 22]
3: [25, 33]
This has a certain amount of value when dealing with enhancing semantics from a string, but it becomes more valuable when you add in named matches as well.
let regex2 = /find (?<Class>.*?) where (?<property>.*?) = (?<expression>.*)/di
let matchObj2 = regex2.exec("Find People where name = Jane Doe");
matchObj2.groups
>{Class: 'People', property: 'name', expression: 'Jane Doe'}
matchObj2.indices.groups
> {Class: [5, 11]
,
expression: [25, 33]
,
property: [18, 22],
}
Of course, if you haven’t played with named groups in Javascript you’re in for a treat, as they can simplify your Javascript code immensely.
Global Await Modules
Now we’re getting into the shiny bicycle part of the new functionality. Importing modules has been a slowly evolving process, as there was a lot of plumbing that needed to be done to handle contentions between asynchronous calls and threads, but the state of the art is getting good enough that importing modules (via the import
keyword is able to handle dynamically generated links. This means that modules can be loaded into a containing module only as they are needed, and different modules can be loaded in to handle different kinds of processing.
One of the bigger stumbling blocks to this was that because these were asynchronous functions, modules themselves couldn’t be called asynchronously because they were global in scope, as asynch functions could only be declared on internal functions. With Ecmascript 2022, that’s been resolved – modules can load other modules asynchronously. Moreover, such global modules do not need to use the (often confusing) async keyword any longer.
For instance, suppose that you needed to grab a user module to populate a display area. The code would look something like this:
import {getUser} from ‘. /User’
await getUser().then((user)=>user.updatePage());
This makes it possible to start downloading multiple modules that can then begin processing as those modules complete loading, while not blocking other modules in the process. This can be used with promises more explicitly as well:
const dataset = await Promise.any([
fetch('http://example.com/data.json')
.then(response => response.json()),
fetch('http://example2.com/data.json')
.then(response => response.json()),
]);
Here, the same routine attempts to grab data from two different servers, and will pass on whichever completes first, a strategy that works especially well with CDNs.
Enhanced Class Methods and Properties
Classes appeared fairly late in the Javascript lineage, in part because a decision by Brendan Eich to use object prototypes rather than classes as a way to get the original versions of Javascript out faster created an entire generation (two or three in fact), who could only conceive of Javascript via function operations. Part of the reason for whole ECMAScript upgrades was in fact to bring classes back into Javascript, but because the paradigms required syntactical differences, this process has taken a number of years to even where we are now. ECMAScript 2022 brings two new (and in my humble opinion very welcome) additions – being able to assign variables to a class without having to write a constructor first, and being able to work with private functions.
Before this iteration, if you wanted to define initial states for variables, these had to be done within constructors:
class HelloWorld {
constructor(){
this.agent = "World";
}
sayIt(){
console.log(`Hello, ${this.agent}!`)
}
}
let hw = new HelloWorld();
hw.sayIt()
=> "Hello, World!"
As the constructor is usually involved in any number of activities, requiring it to also manage initial state can be problematic when objects get complex. With Ecmascript 2022, this is no longer necessary:
class HelloWorldPrivate {
#agent = "World";
sayIt(){
console.log(`Hello,${this.#agent}`)
}
}
let hw2 = new HelloWorld2022();
hw2.sayIt()
=> "Hello, World!"
Of course, in this particular version, you can still change the variable because it’s public:
hw2,agent = "Jane";
hw2.sayIt()
=> "Hello, Jane!
This is where the second (major) innovation comes in. In ES2022, you can also define a private variable using the # character. Private variables are neither accessible nor changeable outside from outside of the class.
class HelloWorldPrivate {
#agent = "World";
sayIt(){
console.log(`Hello,${this.#agent}`)
}
}
let hw3 = new HelloWorldPrivate();
hw3.sayIt()
=> "Hello, World!"
hw3.agent = "Jane";
=> "Hello, World!"
This provides a certain modicum of protection for Javascript, as injection attacks, setting class public variables to different values from external code, has been used in any number of spoofs.
Notice that static variables within classes can be set the same way, just by using the static keyword:
class Tau {
static radians = 2 * Math.pi();
static degrees = 360;
static cycles = 1;
convert(value,var1,var2){
return value * var2 / var1
}
}
Tau.radians/2
=>3.141592653590
Tau.degrees/2
=>180
Tau.cycles = 2
=>1/2
Tau.convert(90,Tau.degrees, Tau.cycles)
=>0.25
Here a static class is built around the concept of Tau, a measure of the circumference of a circle, that is slowly replacing pi in several areas. Because the whole class is static, none of its properties can be changed. This is actually a handy way to create immutable objects for enumeration. This is hinted at in the convert function, which here converts from 90 degrees to 1/4 cycles (a quarter arc of a circle), using the enumerated names to handle the conversion.
Object.hasOwn() and error.cause
These definitely fall into the fruitcake end of Ecmascript gifts, though they do have some utility. When dealing with properties via introspection, you often do not know whether a given property is supported by an object. The Object.hasOwnProperty()
function is a static call that can check before you get hit with an exception for trying to call a method that doesn’t exist, but it tends to be a little flaky around different prototype implementations. Object.hasOwn()
does the same thing, but fixes the prototype error, and also makes for fewer keystrokes which can be important at three in the morning.
When you have nested errors, in try/catch routines, it can often be difficult to find out which error caused the problem. It does so by extending the Error object so that it has a second field called cause that can be used to pass the local error. This will bubble up to the top of the error chain. An example of this is covered here.
Conclusion: Figgy Pudding
I’m not quite sure why I’m writing about Christmas carols and presents in March, but there you go. A shiny bike, a game console, a few sweaters, and a fruitcake or two. All in all a pretty typical year for ECMAScript, which is maturing into a very nice language indeed. Ho ho ho.