The following proposals are likely to make it into this year’s version of ECMAScript:
ECMAScript updates
New versions of JS always create a buzz. Since the ES6 update, there has been a new version every year, and we expect this year (ES2024) to be released around June.
ES6 is a massive release that was released six years after its predecessor, ES5. Browser vendors and JavaScript developers are overwhelmed by the sheer number of new features to adopt and learn. Since then, there has been an annual release cycle in order to prevent such a large drop in new features at the same time.
This annual release cycle involves proposing any new features, which are then discussed, evaluated, and voted upon by a committee before being added to the language. This process also allows browsers to try to implement proposals before they are officially added to the language, which may help resolve any implementation issues.
As mentioned before, new features for JavaScript (or ECMAScript) are decided upon by Technical Committee 39 (TC39) . TC39 is composed of representatives from all major browser vendors as well as JavaScript experts. They meet regularly to discuss new features of the language and how to implement them. New features are proposed as proposals (by anyone), and committee members vote on whether each proposal can advance to the next stage. Each proposal has 4 stages; once a proposal reaches stage 4, it is expected to be included in the next version of ES.
An important part of the ES specification is that it must be backwards compatible . This means that any new features cannot break the Internet by changing the way previous versions of ES worked. Therefore, they cannot change the way existing methods work, only add new ones, as any website running with a possibly pre-existing method will be at risk of crashing.
A full list of all current proposals can be viewed here More
Temporal
In the State of JS 2022 survey , the third most common answer to “What do you think JavaScript is currently missing?” was better date management .
This led to Temporal
a proposal that provides a standard global object to replace this Date
object and fixes some of the issues that have caused a lot of pain to developers when dealing with dates in JavaScript over the years.
Dealing with dates in JavaScript is almost always a horrible task; having to deal with tiny but annoying inconsistencies, like crazy month indexes being zero, but days in months starting at 1.
The difficulties with dates have led to the emergence of popular libraries such as Moment , Day.JS , and date-fns that attempt to solve these problems. However, the Temporal
API is designed to solve all problems natively.
Temporal
Will support multiple time zones and non-Gregorian calendars out of the box, and will provide a simple and easy-to-use API that makes parsing dates from strings easier. Additionally, all Temporal
objects are immutable, which will help avoid any unexpected date change errors.
Let’s look at some examples of the most useful methods provided by the API Temporal
.
Temporal.Now.Instant()
Temporal.Now.Instant()
Will return a DateTime object to the nearest nanosecond. You can from
specify a specific date using:
const olympics = Temporal.Instant.from('2024-07-26T20:24:00+01:00');
This will create a DateTime object indicating that the Paris Olympics later this year will start at 20:24 (UTC) on July 26, 2024.
PlainDate()
This allows you to create only a date, no time:
new Temporal.PlainDate(2024, 7, 26);
Temporal.PlainDate.from('2024-07-26');
// both return a PlainDate object that represents 26th July 2024
PlainTime()
As PlainDate()
a complement, we can use this to create a time without a date, using .PlainTime()
:
new Temporal.PlainTime(20, 24, 0);
Temporal.PlainTime.from('20:24:00');
// both return a PlainTime object of 20:24
PlainMonthDay()
PlainMonthDay()
Similar to PlainDate
, but it only returns the month and day, without year information (useful for dates that recur on the same day every year, such as Christmas and Valentine’s Day):
const valentinesDay = Temporal.PlainMonthDay.from({ month: 2, day: 14 });
PlainYearMonth()
Likewise, there is also the PlainYearMonth
option of returning only the year and month (useful for representing entire months of the year):
const march = Temporal.PlainYearMonth.from({ month: 3, year: 2024 });
calculate
Many calculations can be done using Temporal objects. You can add and subtract various time units to date objects:
const today = Temporal.Now.plainDateISO();
const lastWeek = today.subtract({ days: 7});
const nextWeek = today.add({ days: 7 });
and until
methods since
let you know the time since a specific date or since that date occurred. For example, the following code will tell you how many days until the Paris Olympics:
olympics.until().days
valentinesDay.since().hours
These methods return an Temporal.Duration
object that can be used to measure an amount of time with a variety of different units and rounding options.
Additional features
You can extract the year, month, and day from a Date object, and the hours, minutes, seconds, milliseconds, microseconds, and nanoseconds from a Time object (microseconds and nanoseconds are not available in the current DateTime object). For example:
olympics.hour;
<< 20
There are other properties such as dayOfWeek
(returns 1
Monday and 7
Sunday), daysInMonth
(returns 28
, 29
, 30
or 31
depends on month) and daysinYear
(returns 365
or 366
depends on leap year).
Temporal
Date objects also have a compare
method that can be used to sort dates using various sorting algorithms.
Temporal is currently a Phase 3 proposal that browser vendors are implementing, so it seems like its time has come (pun intended). You can view the full documentation here . There is also a useful use case manual here . When used in conjunction with the Intl.DateTimeFormat API , you will be able to perform some very nifty date operations.
Pipe Operator
In the 2022 State of JS survey , the sixth most popular answer to “What do you think JavaScript is currently missing?” was a pipeline operator .
You can view the Pipe Operator proposal here .
The pipe operator is a standard feature in functional languages that allows you to “pipe” values from one function to another, with the output of the previous function used as the input of the next function (similar to the Fetch API that passes it from One promise returns any data to the next promise).
For example, let’s say we want to apply three functions to a string in succession:
- Concatenates the string “Listen up!” to the beginning of the original string.
- Concatenate three exclamation points to the end of the string.
- Make all text uppercase.
These three functions can be written as follows:
const exclaim = string => string + "!!!"
const listen = string => "Listen up! " + string
const uppercase = string => string.toUpperCase()
These three functions can be applied by nesting them all together as follows:
const text = "Hello World"
uppercase(exclaim(listen(text)))
<< "LISTEN UP! HELLO WORLD!!!"
But deeply nesting multiple function calls like this can get confusing quickly, especially since text
the values ( ) passed as arguments end up being deeply embedded in the expression, making them difficult to identify.
Another problem with function nesting is that the order of application of functions is from back to front, that is, the innermost function is applied first. So in this case, listen
gets is applied to the original value text
, then, and finally exclaim
the outermost function is applied. uppercase
Especially for large and complex functions, this becomes difficult and unintuitive.
Another way is to use a function chain like this:
const text = "Hello World"
text.listen().exclaim().uppercase()
This solves many problems with nested functions. The arguments passed are at the beginning, and each function appears in the order in which it is applied, so listen()
it is applied first, exclaim()
then uppercase()
.
Unfortunately, this example doesn’t work because the listen
and exclaim
functions uppercase
are not methods of the class String
. They can be added by monkey patching classes String
, but this is generally frowned upon as a technique.
This means that, while chaining looks much better than function nesting, it can really only be used with built-in functions (as array methods often do).
Pipes combine the ease of chaining with the ability to use it with any function. Under the current proposal, the above example would look like this:
text |> listen(%) |> exclaim(%) |> uppercase(%)
The token %
is a placeholder used to represent the output value of the previous function, although it %
is likely that this character will be replaced by another character in the official version. This allows functions that accept multiple parameters to be used in pipelines.
Pipes combine the convenience of chaining but can be used with any custom function you write. The only condition is that you need to make sure that the output type of one function matches the input type of the next function in the chain.
Pipelining works best for curried functions that accept only a single argument piped in from the return value of any previous function . It makes functional programming easier because small building block functions can be chained together to form more complex composite functions. It also makes some applications easier to implement.
Despite its popularity, pipeline operators have struggled to move forward with phase two of the process. This is due to disagreements over how to express the symbols and over memory performance and how it works with await
. However, the committee seems to be slowly reaching some kind of agreement, so hopefully the pipeline operators will move through the stages quickly and show up this year.
Thankfully, the pipe operator has been implemented in Babel 7.15 .
Personally, we hope the pipe operator is implemented and launched this year, as it will really help improve JavaScript’s credentials as a serious functional programming language.
Record and tuple
The Record and Tuple proposals aim to bring immutable data structures to JavaScript.
Tuples are similar to arrays (ordered lists of values), but they are deeply immutable . This means that each value in the tuple must be a primitive value or another Record or Tuple (not an array or object, as they are mutable in JavaScript).
Tuples are created like array literals, but #
preceded by a leading hash symbol ( ):
const heroes = #["Batman", "Superman", "Wonder Woman"]
Once created, no other values can be added, nor can any values be deleted. These values cannot be changed either.
Records are similar to objects (collections of key-value pairs), but they are also deeply immutable . They are created like objects – but like tuples, they start with a leading hash:
const traitors = #{
diane: false,
paul: true,
zac: false,
harry: true
}
Records will still use dot notation to access properties and methods:
traitors.paul
<< true
The square bracket notation used for arrays can also be used for tuples:
heroes[1]
<< "Superman"
But since they are immutable, you cannot update any properties:
traitors.paul = false
<< Error
heroes[1] = "Supergirl"
<< Error
The immutability of tuples and records means you will be able to ===
easily compare them using operators:
heroes === #["Batman", "Superman", "Wonder Woman"];
<< true
One thing to note is that the order of attributes doesn’t matter when considering equality of records:
traitors === #{
ross: false,
zac: false,
paul: true,
harry: true
};
// still true, even though the order of people has changed
<< true
Order does matter for tuples, though , since they are ordered lists of data :
heroes === #["Wonder Woman", "Batman", "Superman"];
<< false
This page has a handy tutorial with a live playground so you can get used to how records and tuples work.
Regular expression/v flag
Regular expressions have been included in JavaScript since version 3, and there have been many improvements since then (such as u
Unicode support using flags in ES2015). A logo v
proposal aims to do u
everything a logo does, but it adds some extra benefits, as we’ll see in the example below.
Simply put, implementing this v
flag requires /v
adding a at the end of the regular expression.
For example, you can use the following code to test whether a character is an emoji:
const isEmoji = /^\p{RGI_Emoji}$/v;
isEmoji.test("💚");
<< true
isEmoji.test("🐨");
<< true
This uses RGI_Emoji
patterns to recognize emojis.
This v
flag also allows you to use settings notation in regular expressions. For example, you can use --
operators to subtract one pattern from another. The following code can be used to remove any heart from the emoji set:
const isNotHeartEmoji = /^[\p{RGI_Emoji_Tag_Sequence}--\q{💜💚♥️💙🖤💛🧡🤍🤎}]$/v;
isNotHeartEmoji.test("💚");
<< false
isNotHeartEmoji.test("🐨");
<< true
You can find the intersection of two patterns using &&
. For example, the following code will find the intersection of Greek symbols and letters:
const GreekLetters = /[\p{Script_Extensions=Greek}&&\p{Letter}]/v;
GreekLetters.test('π');
<< true
GreekLetters.test('𐆊');
<< false
This v
flag also resolves u
some issues with the flag’s case insensitivity, making it a better choice for use in almost all situations.
The regular expression flag v
reached stage 4 in 2023 and has been implemented in all major browsers, so it is fully expected to become part of the ES2024 specification.
Decorator
The Decorator proposal aims to use decorators to natively extend JavaScript classes.
Decorators are already common in many object-oriented languages (such as Python) and are already included in TypeScript . They are standard metaprogramming abstractions that allow you to add extra functionality to a function or class without changing its structure. For example, you might want to add some additional validation to a method, which you can do by creating a validation decorator that checks the data entered into the form.
While JavaScript allows you to implement this design pattern using functions, most object-oriented programmers prefer a simpler, more native way of doing this, just to make life easier.
This proposal adds some syntactic sugar that allows you to easily implement decorators in classes without having to think about binding this
to the class. It provides a cleaner way to extend class elements such as class fields, class methods or class accessors and can even be applied to the entire class.
Decorators are identified by a symbol’s prefix @
and are always placed immediately before the code they are “decorated”.
For example, a class decorator would immediately precede the class definition. In the following example, validation
the decorator is applied to the entire class FormComponent
:
@validation
class FormComponent {
// code here
}
// The decorator function also needs defining
function validation(target) {
// validation code here
}
A class method decorator immediately precedes the method it decorates. In the following example, validation
the decorator is applied to the submit
method:
class FormComponent {
// class code here
@validation
submit(data) {
// method code here
}
}
// The decorator function also needs defining
function validation(target) {
// validation code here
}
The decorator function definition accepts two parameters: value and context. The value parameter refers to the modified value (such as a class method), and the context contains metadata about the value, such as whether it is a function, its name, and whether it is static or private. You can also add an initialization function to the context, which will be run when the class is instantiated.
The Decorator proposal is currently in Phase 3 and has been implemented in Babel , so you can already try it out.