ECMAScript Updates

The release of a new JavaScript version always generates excitement. Following the ES6 update, a new version has been introduced annually, and we anticipate the arrival of this year’s edition (ES2024) around June.

ES6 was a significant update, arriving six years after its predecessor, ES5. The multitude of new features introduced left browser vendors and JavaScript developers eager yet overwhelmed. To avoid such a large influx of features at once, a yearly release cycle was established.

This cycle entails proposing new features, which are subsequently discussed, evaluated, and voted on by a committee before they are integrated into the language. This approach also enables browsers to experiment with these proposals prior to their official inclusion, helping to identify any potential implementation issues.

As previously mentioned, new features for JavaScript (or ECMAScript) are determined by Technical Committee 39 (TC39). This committee consists of representatives from all major browser vendors, as well as JavaScript specialists. They convene regularly to discuss new language features and their implementations. Proposals can be submitted by anyone, and committee members then vote on whether to advance each proposal to the next stage. Each proposal undergoes four stages; once a proposal reaches Stage 4, it is expected to be included in the forthcoming ES version.

A crucial aspect of the ES specification is its requirement for backward compatibility. This means that new features cannot disrupt existing Internet functionality by altering how previous versions of ES operate. Therefore, while new methods can be introduced, existing methods cannot be changed, as doing so could potentially break websites reliant on those methods.

You can view the complete list of current proposals here.

Temporal

In the 2022 State of JS survey, the third most frequent response to the question, “What do you believe is currently lacking in JavaScript?” was improved date management.

This feedback has led to the Temporal proposal, which aims to provide a standard global object intended to replace the Date object and address many of the longstanding issues that have frustrated developers when handling dates in JavaScript.

Working with dates in JavaScript is often a challenging task, fraught with irritating inconsistencies—such as the confusion of zero-indexed months contrasted with days starting at 1.

These challenges have resulted in the emergence of popular libraries like Moment, Day.js, and date-fns, which aim to alleviate these issues. However, the Temporal API seeks to resolve these problems natively.

The Temporal API will support multiple time zones and non-Gregorian calendars by default and will feature an intuitive API to simplify date parsing from strings. Moreover, all Temporal objects will be immutable, reducing the risk of accidental date modification errors.

Let’s examine some of the most useful methods provided by the Temporal API.

Temporal.Now.Instant()

Temporal.Now.Instant() returns a DateTime object accurate to the nearest nanosecond. You can create specific dates using the from method like this:

const olympics = Temporal.Instant.from('2024-07-26T20:24:00+01:00');

This line of code generates a DateTime object representing the beginning of the Paris Olympics on July 26, 2024, at 20:24 (UTC).

PlainDate()

The Temporal.PlainDate() function allows you to create a date without any associated time:

new Temporal.PlainDate(2024, 7, 26);

You can also create a PlainDate object from a string:

Temporal.PlainDate.from('2024-07-26');

Both of these methods will yield a PlainDate object representing July 26, 2024.

PlainTime()

Complementing PlainDate(), the Temporal.PlainTime() function enables the creation of a time without a date:

new Temporal.PlainTime(20, 24, 0);

You can also create a PlainTime object from a string:

Temporal.PlainTime.from('20:24:00');

Both examples will result in a PlainTime object set to 20:24.

PlainMonthDay()

The Temporal.PlainMonthDay() function is similar to PlainDate, but it only returns the month and day without any year information. This is particularly useful for recurring dates, such as Christmas and Valentine’s Day:

const valentinesDay = Temporal.PlainMonthDay.from({ month: 2, day: 14 });

PlainYearMonth()

Likewise, the Temporal.PlainYearMonth() function returns just the year and month, which is helpful for representing an entire month within a year:

const march = Temporal.PlainYearMonth.from({ month: 3, year: 2024 });

Calculations

You can perform various calculations with Temporal objects, including adding and subtracting different units of time to a date object:

const today = Temporal.Now.plainDateISO();

const lastWeek = today.subtract({ days: 7 });
const nextWeek = today.add({ days: 7 });

The until and since methods allow you to determine the amount of time until a specific date or the duration since that date occurred. For example, the following code snippet shows how many days remain until the Paris Olympics:

olympics.until().days;

And to find out how many hours have passed since Valentine’s Day, you can use:

valentinesDay.since().hours;

These methods return a Temporal.Duration object that can measure time in various units and provide different rounding options.

Extras

You can extract the year, month, and day from a Date object, as well as the hours, minutes, seconds, milliseconds, microseconds, and nanoseconds from a Time object (note that microseconds and nanoseconds are not yet available in the current DateTime object). For example:

olympics.hour; 
// returns 20

Additionally, there are other properties you can access, such as dayOfWeek (which returns 1 for Monday and 7 for Sunday), daysInMonth (which returns 28, 29, 30, or 31 depending on the month), and daysInYear (which returns 365 or 366, depending on whether it’s a leap year).

The Temporal date objects also include a compare method, allowing you to order dates using various sorting algorithms.

Currently, Temporal is at Stage 3 in the proposal process and is being implemented by browser vendors, indicating that its time has indeed come (pun intended). You can find the complete documentation here. There is also a helpful cookbook of use cases available here. When combined with the Intl.DateTimeFormat API, you’ll be able to perform some impressive date manipulations.

Pipe Operator

In the State of JS 2022 survey, the sixth most common response to the question, “What do you feel is currently missing from JavaScript?” was the Pipe Operator.

You can find the Pipe Operator proposal here.

The pipe operator is a standard feature in functional programming languages that allows you to “pipe” a value from one function to another. This means the output of one function is used as the input for the next, much like how the Fetch API passes data from one promise to the next.

For instance, if we want to apply three functions consecutively to a string:

  1. Prepend the string “Listen up!” to the original string.
  2. Append three exclamation marks to the end of the string.
  3. Convert all text to uppercase.

These functions can be defined as follows:

const exclaim = string => string + "!!!";
const listen = string => "Listen up! " + string;
const uppercase = string => string.toUpperCase();

They can be applied through nested function calls like this:

const text = "Hello World";

uppercase(exclaim(listen(text))); 
// returns "LISTEN UP! HELLO WORLD!!!"

However, deeply nesting multiple function calls can quickly become unwieldy, especially since the value (text) is deeply embedded within the expression, making it hard to identify.

Another issue with nested functions is that they are applied in reverse order; the inner functions are executed first. In the example above, listen processes the original text, followed by exclaim, with uppercase being the last to apply. This can be difficult and unintuitive to track, especially with larger, more complex functions.

An alternative approach is function chaining:

const text = "Hello World";

text.listen().exclaim().uppercase();

This method resolves many problems associated with nested functions. The argument is clearly at the beginning, and each function appears in the order of application: listen() is called first, followed by exclaim(), and finally uppercase().

However, this approach will not work as intended because the listen, exclaim, and uppercase functions are not methods of the String class. While you could theoretically add these methods through monkey patching, this practice is generally discouraged.

Thus, while chaining looks cleaner than nesting, it can only be effectively used with built-in functions, as is commonly done with Array methods.

The pipe operator merges the ease of chaining with the flexibility to use any functions. Under the current proposal, the example would be expressed as follows:

text |> listen(%) |> exclaim(%) |> uppercase(%);

The % symbol serves as a placeholder representing the output value from the previous function, although it is likely that this character will be replaced in the final release. This design allows functions that accept multiple arguments to be used within the pipeline.

Piping combines the simplicity of chaining with the ability to utilize any custom functions you’ve defined. The only requirement is that the output type of one function must match the input type of the subsequent function in the chain.

Piping is particularly effective with curried functions that only accept a single argument, which is passed from the return value of any preceding function. This approach facilitates functional programming, enabling small, modular functions to be combined into more complex composite functions and simplifying the implementation of partial application.

Despite its popularity, the pipe operator has faced challenges in advancing beyond Stage 2 of the proposal process. Disagreements over the notation and concerns regarding memory performance and compatibility with await have delayed progress. However, it appears the committee is gradually reaching consensus, so we may see the pipe operator move quickly through the stages and potentially make its debut this year.

Fortunately, the pipeline operator has been available in Babel since version 7.15.

We would personally love to see the pipe operator implemented and released this year, as it would significantly enhance JavaScript’s standing as a serious functional programming language.

Records and Tuples

The Records and Tuples proposal aims to introduce immutable data structures to JavaScript.

Tuples are akin to arrays, serving as ordered lists of values, but they are deeply immutable. This means every value in a tuple must be either a primitive value or another record or tuple—not arrays or objects, as those are mutable in JavaScript.

You can create a tuple similarly to an array literal, but with a leading hash symbol (#):

const heroes = #["Batman", "Superman", "Wonder Woman"];

Once created, you cannot add or remove values, nor can you change the existing values.

Records, on the other hand, resemble objects in that they are collections of key-value pairs, but they too are deeply immutable. They are created in a manner similar to objects, but like tuples, they begin with a leading hash:

const traitors = #{
  diane: false,
  paul: true,
  zac: false,
  harry: true
};

You can still access properties and methods using dot notation:

heroes[1]; // returns "Superman"

However, since they are immutable, attempting to update any properties will result in an error:

traitors.paul = false; // Error
heroes[1] = "Supergirl"; // Error

The immutability of tuples and records allows for straightforward comparisons using the === operator:

heroes === #["Batman", "Superman", "Wonder Woman"]; // returns true

It’s important to note that the order of properties does not affect equality when comparing records:

traitors === #{
  ross: false,
  zac: false,
  paul: true,
  harry: true
}; // returns true, even with reordered properties

Conversely, the order does matter for tuples, as they represent an ordered list of data:

heroes === #["Wonder Woman", "Batman", "Superman"]; // returns false

This page also includes a handy tutorial with a live playground to help you practice how records and tuples will function.

RegExp /v Flag

Regular expressions have been a part of JavaScript since version 3, with numerous enhancements over the years, including Unicode support via the u flag introduced in ES2015. The /v flag proposal aims to extend the capabilities of the u flag by introducing additional features, as demonstrated in the examples below.

To use the /v flag, simply append it to the end of your regular expression.

Example: Testing for Emojis

For instance, the following code checks if a character is an emoji:

const isEmoji = /^\p{RGI_Emoji}$/v;

isEmoji.test("💚"); // returns true
isEmoji.test("🐨"); // returns true

Here, the RGI_Emoji pattern is utilized to identify emojis.

Set Notation and Subtraction

The /v flag also enables the use of set notation in regular expressions, allowing you to subtract one pattern from another with the -- operator. The following code removes any love hearts from the set of emojis:

const isNotHeartEmoji = /^[\p{RGI_Emoji_Tag_Sequence}--\q{💜💚♥️💙🖤💛🧡🤍🤎}]$/v;

isNotHeartEmoji.test("💚"); // returns false
isNotHeartEmoji.test("🐨"); // returns true

Intersection of Patterns

You can also find the intersection of two patterns using &&. For example, the following code finds the intersection of Greek symbols and letters:

const GreekLetters = /[\p{Script_Extensions=Greek}&&\p{Letter}]/v;

GreekLetters.test('π'); // returns true
GreekLetters.test('𐆊'); // returns false

Improvements Over the u Flag

Additionally, the /v flag addresses certain issues related to case insensitivity present in the u flag, making it a superior choice in most scenarios.

The /v flag for regular expressions reached Stage 4 in 2023 and has been implemented in all major browsers, so it is expected to be included in the ES2024 specification.

Decorators in JavaScript

The Decorator proposal aims to introduce native decorators for extending JavaScript classes. Decorators are already prevalent in various object-oriented languages, such as Python, and have been integrated into TypeScript. They provide a standard metaprogramming abstraction that allows developers to enhance functions or classes without altering their fundamental structure. For instance, a validation decorator could be created to add extra checks to data entered into a form.

While JavaScript permits the use of functions to implement this design pattern, many object-oriented programmers prefer a more straightforward and native approach to simplify the process.

Simplifying Class Extensions

The Decorator proposal adds syntactic sugar that enables easier implementation of decorators within a class, eliminating the need to bind the decorator to the class explicitly. This results in a cleaner method for extending class elements, including fields, methods, accessors, and even entire classes.

Decorators are indicated by a prefix @ and are placed immediately before the code they modify.

Example of a Class Decorator

For example, a class decorator can be applied directly before the class definition:

@validation
class FormComponent {
  // class code here
}

// The decorator function needs to be defined
function validation(target) {
  // validation code here
}

Example of a Method Decorator

Similarly, a method decorator is positioned immediately before the method it decorates:

class FormComponent {
  // class code here

  @validation
  submit(data) {
    // method code here
  }
}

// The decorator function needs to be defined
function validation(target) {
  // validation code here
}

Decorator Function Parameters

Decorator function definitions accept two parameters: value and context. The value argument refers to the decorated item (such as a class method), while the context provides metadata about that value, including its type (function or not), its name, and whether it’s static or private. Additionally, you can include an initializer function in the context, which will be executed when a class is instantiated.

The Decorator proposal is currently in Stage 3 and has been implemented in Babel, allowing developers to experiment with it.

Conclusion

What are your thoughts? What features would you like to see added to the JavaScript specification this year? These proposed enhancements promise to be valuable additions to the language, so let’s hope they make it into the official release soon!

Shares: