DEV Community

loading...

Status of JavaScript(ECMAScript): 2019 & beyond.

netil profile image Jae Sung Park Updated on ・18 min read

JavaScript continues its popularity that it doesn’t necessary to mention it anymore.

JavaScript is the most commonly used language in GitHub over the years, as shown in the graph in GitHub’s report, Octoverse. Also Stack Overflow’s recent survey, “Developer Survey Results 2019”, JavaScript was named as the most popular technology.

Alt text of image
Top languages over time used on GitHub

In particular, ECMAScript 2015 and subsequent ECMAScript specifications seem to be accepted by many developers without much resistance.


JavaScript Flavors from the ‘State of JS 2018’ survey.

In this article we will look at the key features of ECMAScript 2018 and the proposals expected to be included in the following specifications, ECMAScript 2019 and ECMAScript 2020.

Bear in mind:
The content of this article is based on June 2019. Depending on the time you read, the content and facts of the article may vary.

Some changes & news

Although it does not directly affect the linguistic aspects of JavaScript, there have been some changes that could affect indirect aspects, such as use environments and ecosystems.

Changes in TC39

TC39, a technical committee discussing JavaScript standard specifications, has decided to change its operating structure from 2019. TC39 holds meetings six times a year, and the committee has grown to a point where about 40 to 60 people attend meetings.

The structure, which used to be the chairman and vice chairman, changed to a horizontal structure in the form of shared responsibility by three co-chairs (Aki Braun (PayPal), Brian Terlson (Microsoft), and Yulia Startsev (Mozilla). It also opened its official website(https://tc39.github.io) in March 2019.

In the article “A Year (plus a little) on TC39”, written by Aki Braun, co-chair of TC39, you can see the progress of the TC39 meeting and the appearance of the meeting.

Earlier in July 2018, npm joined ECMA International and TC39 (see “npm Joins ECMA International and TC39”).

SharedArrayBuffer

Due to the security vulnerabilities Meltdown and Spectre, browser vendors have changed their default settings to disable the use of SharedArrayBuffer object as of January 5, 2018.

These settings are still being maintained to this day, except Chrome. Chrome reactivated since v67, through the site isolation (see “Isue 821270: Re-enable SharedArrayBuffer + Atomics”).

The adoption of chromium by MS Edge

Microsoft surprised many by announcing on December 6, 2018 that it would transitioning its browser, Microsoft Edge, to Chromium-based (see “Microsoft Edge: Making the Webbetter through more open source collaboration.”)

In a Q&A session with Microsoft held on the sidelines of the TC39 meeting on Jan. 29, 2019, the following facts were revealed regarding Microsoft Edge’s Chromium transition.

There’s no plan on open sourcing the old rendering engine.
The updates of the existing JavaScript engine, ChakraCore, will continue but has no long-term plans.

Limin Zhu, member of ChakraCore Team, commented as:

ChakraCore is currently being used in various projects outside the browser. So, despite the change of direction for Microsoft Edge, our team will continue supporting ChakraCore (see “Chromium adoption in Microsoft Edge and future of ChakraCore”).

You can download chromium based Microsoft Edge(Canary/Dev/Beta version) from the Microsoft Edge Insider Channels site.

The purpose and future plans for transforming Microsoft Edge to Chromium-based can be found in the “Microsoft Edge and Chromium Open Source: Our Intent” article.

From the developer’s perspective, this transition of Microsoft Edge can reduce the burden & struggles of cross-browsing development.

However, from the perspective of the Web ecosystem, something worrisome could happen. Because this will cut the diversity of the browsers.

If you remember the time when Internet Explorer was in a monopolistic position, where many websites were targeted to Internet Explorer only, the expansion of chromium-based browsers would not be a pleasant.

For the concerns about diversity, see the article “Chrome is turning into the new Internet Explorer 6” and Mozilla’s “Goodbye, EdgeHTML”.

Module Support

Since the module support has been added in ECMAScript 2015, modules are widely used as an essential functionality now.

Let’s take a look the coverage and the current status of the dynamic import() syntax and native module support to further expand of the use of the module.

Dynamic import()

Promise based import() syntax, will let load module dynamically. This proposal was staying at Stage 3 for awhile, but finally 6th June, reached ‘Stage 4’ and become a part of the ECMAScript 2020.

import("./myModule.mjs")  
    .then(module => {
        ...
    });
// using async/await
(async () => {
    const module = await import("./myModule.mjs");
    ...
})();

Starting from Firefox 60, the import() syntax can be used by setting the javascript.options.dynamicImport flag, and this was enabled by default in Firefox 67.

Microsoft Edge(non chromium based) does not support import() syntax yet, but it would expected to be supported when chromium based Edge is released.

Native Module loading

Starting with Firefox 60 released in May 2018, native modules(ESM) can be used without flag(see “Firefox 60 — Modules and More”). Node.js 8.5.0, released by September 2017, experimentally supports ESM.

ESM in Node.js requires --experimental-modules flag as the below example. In this occasion, CommonJS’s ‘require()’ will be disabled for module loading (see “Announcering a new — experimental-modules”).

node --experimental-modules my-app.mjs

The Node.js foundation, formed ‘Module Team’ for the official support of ESM. The work of the Module Team is split into 4 phases.

  • Phase 0: Branches off of current Node but removes much of the Node 8.5.0+ --experimental-modules implementation.
  • Phase 1: Adds the “minimal kernel,” features that the modules working group felt would likely appear in any potential new ES modules implementation.
  • Phase 2: Fleshes out the implementation with enough functionality that it should be useful to average users as a minimum viable product.
  • Phase 3: Improves user experience and extends the MVP. The effort is currently in Phase 3.

ECMAScript 2018

ECMAScript 2018 was announced in June 2018.

Asynchronous Iterators

The async operator, which enumerates asynchronous stream data, operates similarly to a typical operator and uses a syntax form for — await — of. The difference between an async operator and a normal operator is that it returns of Promise object.

async function test() {  
    // Regular Iterator
    for (const i of list) ...

    // Async Iterator
    for await (const i of asyncList) ...
}

If you are dealing with asynchronous call streams, you can create an async operator factory.

// example from: https://jakearchibald.com/2017/async-iterators-and-generators/
async function* asyncRandomNumbers() {
    const url = "https://www.random.org/integers/?num=1&min=1&max=100&col=1&base=10&format=plain&rnd=new";

   while (true) {
        const response = await fetch(url);
        const text = await response.text();
        yield Number(text);
    }
}

(async function() {
    let i = 0;

    for await (const number of asyncRandomNumbers()) {
        console.log(++i, "=>", number);
        if (i === 10) break;
    }
})();

// 1 "=>" 65
// 2 "=>" 62
// ...

Object Rest/Spread Properties

Like Rest parameter and Spread operator spec from ECMAScript 2015, this proposal introduces object destructuring assignment and spread properties for object literals.

// Rest property
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };  
x; // 1  
y; // 2  
z; // { a: 3, b: 4 }

// Spread property
let n = { x, y, ...z };  
n; // { x: 1, y: 2, a: 3, b: 4 }

The drop of Template Literal syntactic restrictions
The template literal removed restrictions on the use of escape sequences (see “Template Literal Revision”).

A tagged template literal is a function to receive the template and return a modified string. The string passed to the function can be one of the following types:

  • Cooked: Escape sequence are interpreted
  • Raw: Escape sequences are normal text. The non-interpreted value in the template string are processed by the String.raw() method.
function tagFunc(str, substs) {  
    return str;
}

const result = tagFunc`\u{4B}`;  
result;  // ["K"]  
result.raw; // ["\u{4B}"]

Previously, if the template had some sequences of characters after backslash, it was treated as illegal and no raw string was returned.

  • \u: Unicode (e.g. \u004B)
  • \x: Hex (e.g. \x4B)
  • \positive: octal (e.g. \141)

ECMAScript 2018 eliminates all of the syntactic restriction related to escape sequences and returns the string in raw form. In this case, the interpreted value returns undefined.

const result = tagFunc`\131`;  
result;  // [undefined]  
result.raw; // ["\131"]

Checkout “ES2018: Template Literal Revision” for more information about the problems and solutions in the template literal.

Promise.prototype.finally

Like the finally syntax of the try...catch statement, this proposal introduces the similarity usage for Promise object.

The finally syntax is the block of code that is executed unconditionally at the end, regardless of the processing state of the Promise object(‘resolve’ or ‘reject’). Once a Promise object is called, this code block will be performed regardless of the result.

let isLoading = true;
fetch("https://www.random.org/integers/?num=1&min=1&max=100&col=1&base=10&format=plain&rnd=new")  
.then(function(res) {
    if(res) {
        return res.text();
    }
    throw new TypeError("Error");
})
.then(function(text) { /* Success */ })
.catch(function(error) { /* Error */ })
.finally(function() {
    // will be performed regardless success or error
    isLoading = false;
});

Checkout more details from MDN’s Promise.prototype.finally documentation.

Regular Expression

Has been added several regular expression proposals.

s (dotAll) flag for regular expressions

The dot(.) matches all characters, but not against for \r nor \n. To solve this problem, introduces the new flag s.

// previous
/./test("a");  // true
/./.test("\n");  // false
// dotAll flag
/./s.test("\n");  // true

RegExp named capture groups

Provides the ability to name a capture group. The(?<name>pattern), will let adds the <name> to the capture group pattern, and then uses the name as a reference the capture.

const rx = /(?<year>[0-9]{4})-(?<month>[0-9]{2})/;  
const match = rx.exec('2018-03');  
match.groups.year; // 2018  
match.groups.month; // 03

RegExp Lookbehind Assertions

In regular expressions, the values of a particular pattern are followed (lookahed) or not followed(negative lookaheds) by a string.
This proposal, by contrast, provides the ability to look for particular pattern to get ahead(lookbehind) or not get ahead(negative lookbehind) of.

// positive lookahead
/aa(?=bb)/.test("aabb"); // true
// negative lookahead
/aa(?!bb)/.test("aac");  // true
// positive lookbehind
/(?<=aa)bb/.test("aabb");  // true
// negative lookbehind
/(?<!=aa)bb/.test("bbbb");  // true

RegExp Unicode Property Escapes

Unicode property escapes are a new type of escape sequence available in regular expressions that have the u flag set.

/^\p{White_Space}+$/u.test('\t \n\r');  // true /^\p{Script=Greek}+$/u.test('μετά');  // true

ECMAScript 2019

ECMAScript 2019 is in the Candidate Draft state. As seeing the previous release dates, the final release is expected to be announced around June 2019.

Array.prototype.flat() / Array.prototype.flatMap()

The Array.prototype.flat() method and the Array.prototype.flatMap() method recursively find sub-array elements to a specified depth and creates a new array concatenated into it.

// Array.prototype.flat
[1, 2, [3, 4]].flat();  // [1, 2, 3, 4]
[1, 2, [3, 4, [5, 6]]].flat(1);  // [1, 2, 3, 4, [5, 6]]
// Array.prototype.flatMap
[1, 2, 3, 4].map(x => [x * 2]);  // [[2], [4], [6], [8]]
[1, 2, 3, 4].flatMap(x => [x * 2]);  // [2, 4, 6, 8]

Short story about naming .flat():

The initial proposed name was “.flatten()”. But, MoTools(widely used legacy library), was already offering a similar functionality under the same name ‘array.prototype.flatten()’. If the name “.flaten()” is accepted as is, it is likely having issue for all those websites that uses MoTools.

To avoid the possibility of conflict, .flatten() was renamed to .flat().

  • 1) The name of ‘.smoosh()’ was one of the candidates.
  • 2) Also some suggested change the behavior of MoTools’ flatten() to work as ECMAScript 2019 (see “Update Array.prototype.flaten to match TC39 proposal”). But, this can make stop working many no longer updated websites, like Tom Dale tweeted(who participated Ember and SproutCore development).

Object.fromEntries()

Object.fromEntries(), transforms a list of key-value pairs into an object.

const entries = new Map([  
    ['foo', 'bar'],
    ['baz', 42]
]);

Object.fromEntries(entries);  // { foo: "bar", baz: 42 }

String.prototype.trimStart() / .trimEnd()

Removes whitespace from the beginning (String.prototype.trimStart() — aliased ‘.trimLeft()’) or from the end(String.prototype.trimEnd() — aliased ‘.trimRight()’) of a string.

const greeting = "   Hello world!   ";

greeting.trimStart();  // "Hello world!   "  
greeting.trimEnd();  // "   Hello world!"

Symbol.prototype.description property

Symbol.prototype.description property returns an optional read-only description of Symbol objects.

// will return 'Test Description'
Symbol("Test Description").description;

Optional catch binding

Optional catch binding proposal, is to allow parameters to be omitted if parameters are not used in the catch syntax in the try...catch statement.

// Traditional way
try { ··· } catch(e) { ··· }

// Optional catch binding
// if don't need the use of parameter, it can be omitted
try { ··· } catch { ··· }

Array.prototype.sort() stability

Array.prototype.sort() method used a unstable quicksort algorithm, when sorting arrays with more than 10 elements. To ensure that the array is aligned correctly, ECMAScript 2019 uses the Timsort algorithm for the Array.prototype.short().

This specification currently works well with all JavaScript engines. However, Microsoft Edge with ChakraCore generates a sort error with array containing 512+ elements.

The below screenshot illustrates the stability test results on Edge and Firefox. As you can see Edge is failing.


Stability test result: (Left) Microsoft Edge 17.17134 / (Right) Firefox 66.0.2

Checkout more detailed discussion from: “Array#sort stability”.

Well-formed JSON.stringify

RFC 8259 specifies JSON text to be encoded as UTF-8 format, for JSON object data exchange. But when JSON.stringify() is used, some UTF-16 codes (characters from 0xD800 to 0xDFFFF, which are classified as ‘surrogate’) aren’t encoded in UTF-8.

The ECMAScript 2019, returns an escape sequence instead of returning an invalid Unicode string, as shown(Edge) in the following illustration.

JSON.stringify() results on Edge and Firefox

For more details checkout the proposal documentation.

Subsume JSON

ECMAScript claims JSON as a subset in [JSON.parse](https://tc39.github.io/ecma262/#sec-json.parse), but this is not true because JSON strings can contain unescaped U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR characters.

This proposal suggests ECMA-262 can be extended to allow these characters to not not break the JSON to be subset of ECMAScript.

// if ECMA is superset of JSON, these must be true
eval('"\u2028"') === "\u2028"  // true  
eval('"\u2029"') === "\u2029"  // true

For more details checkout the proposal doc: Subsume JSON (a.k.a. JSON ⊂ ECMAScript)

Function.prototype.toString revision

From the definition in ECMAScript 2016, the result of Function.prototype.toString() can vary depend the engines. ECMAScript 2019 ensures to return the original code as it was defined (see “Function.prototype.toString revision”).

When returning code defined in a function, ECMAScript 2019 uses the following algorithm to return code string defined in a function:

  • Line breaks: \r\n(Windows) or \n(macOS) all returns as Unix-style \n.
  • Built-in function: All codes (mainly built-in functions) not defined via ECMAScript, will return as [native code].
isNaN.toString();  // "function isNaN() { [native code] }"

The result of “isNaN.toString()” in different browsers<br>

  • Functions created dynamically via Function and GeneratorFunction:
    Engines must create the appropriate source code and attach it to the functions.

  • In all other cases:
    throw a TypeError.

Checkout more details from: ES proposal: Function.prototype.toString revision

ECMAScript 2020

As of March 1, 2019, TC39 repo master branch was updated to point next ECMAScript 2020. (see the commit log).

At this point, finished proposals(Stage 4) for ECMAScript 2020, are String.prototype.matchAll() and import() only, but as times goes these list will include more items.

The String.prototype.matchAll() method works similarly as the String.prototype.match(). The former returns an iterator that contains the matched string and details of match, when it used with the g(global)/y(sticky) flag.

const str = "test1test2";  
const rx = /t(e)(st(\d?))/g;

str.match(rx);  // ["test1", "test2"]

for (const match of str.matchAll(rx)) {  
    // 1: ["test1", "e", "st1", "1", index: 0, input: "test1test2"]
    // 2: ["test2", "e", "st2", "2", index: 5, input: "test1test2"]
    match;
}

Some new or incomplete proposals

Let’s take a look at some interesting proposals were not in the final stage yet.

globalThis

Typically to access the top-level object is through the ‘window’ object in a browser environment.

As of the expansion of execution environment, the way to access top-level object also been changed.

  • In a Node.js environment, top-level object is accessed through the ‘global’ objects.
  • From the HTML5 specification, there are ‘Window’ and ‘WindowProxy’ for this, whereas ECMAScript 2015 specification, both objects are treated same way to access top-level object.

Checkout “Inner and outer windows” for the differences between Window and WindowProxy object.

The below code is the simplest way to get the reference of the top-level objects(the “global”) regardless of the environment. But this approach causes Content Security Policy (CSP) violation in Chrome apps. (see “es6-shim Breaks Chrome App CSP”).

var global = Function('return this')();

The well-know ES6 compatibility shims library, ES6 shim, use below function to get the global object and this is the common and best way at this moment.

// https://github.com/paulmillr/es6-shim/blob/003ee5d15ec1b05ae2ad5ddad3c02fcf8c266e2c/es6-shim.js#L176
var getGlobal = function () {  
    /* global self, window, global */
    // the only reliable means to get the global object is
    // `Function('return this')()`
    // However, this causes CSP violations in Chrome apps.
    if (typeof self !== 'undefined') { return self; }
    if (typeof window !== 'undefined') { return window; }
    if (typeof global !== 'undefined') { return global; }
    throw new Error('unable to locate global object');
};

The new proposal ‘globalThis’ is to provide the way to access top-level and get rid the discomfort depending the environment. Currently is staying at ‘Stage 3’. Not finalized yet, but in Chrome 71 and Firefox 65, and Node.js 12, globalThis objects can be used as follows:

globalThis.setTimeout;  // window.setTimeout

About the ‘globalThis’ name:

Similar as the ‘Array.prototype.flat()’ case, the first proposed name was “global”. But the use of this name, reported breaking flickr.com, which let to change the name to ‘globalThis’.

Class field declarations

The use of class field declarations are available starting with Babel 7.1.0 (released September 17, 2018). However, this proposal isn’t reached final stage yet, staying ‘Stage 3’ at this moment.

This proposal introduces declarative syntax for class variables in a more intuitive and easier way.

Initialization

To initialize instance variable is through the constructor.

class MyClass {  
    constructor() {
        this.x = 1;
        this.y = 2;
    }
}

With the class field, the instance variables can be defined as the //Initializer part of the following code and the initialization area runs before the constructor runs.

class MyClass {  
    // Initializer
    x = 1;
    y = 2;
    log = console.log("Initializer");

    constructor() {
        console.log("Constructor:", this.x, this.y);
    }
}

new MyClass();  
// Initializer
// Constructor: 1 2
private declaration

At the time where JavaScript wasn’t providing the way to declare ‘private’, many developers used underscore(‘_’) as prefix as a convention. But this wasn’t making realistically to work as private(well, there is a way to actually make the variable or method work as private).

function MyClass() {  
    this._x = 1;
    this._y = 2;
}

MyClass.prototype.getX = function() {  
    return this._x;
}

The private declarator uses the number symbol(#) as a prefix to explicitly declare that it is a private. Variables or methods starting with ‘#’ can only be accessed within class blocks.

class MyClass {  
    #foo; // private field
    constructor(foo) {
        this.#foo = foo;
    }
    incFoo() {
        this.#foo++;
    }
}
Declaration and access

The following is a simple example of declaring and accessing class fields in various forms.

class MyClass {  
    A = 1;  // (a) instance field
    static B = 2;  // (b) static class field
    #C = 3;  // (c) private field

    getPrivate() {
        return this.#C;
    }
}

new MyClass().A;  // 1
MyClass.B;  // 2
new MyClass().getPrivate();  // 3

Checkout the ES proposal: class fields for more details.

Built-in Module

The current ‘Stage 1’ built-in module specification is same as ESM. The difference of the normal ESM is that these are “built-in” and distributed with the browser itself.

Built-in module, is not directly exposed to global. Only available through the import syntax. If the browser has support on built-in module, these modules are imported with the “std:” prefix + module name, as shown in the following example. In this example, loads KV Storage Module.

import {storage, StorageArea} from "std:kv-storage";

The KV Storage Module and Import maps proposal is closely linked to the build-in module specification. These two are not part of ECMAScript specification and they’re belongs to the WICG(Web Incubator Community Group).

KV Storage Module

Chrome 74, adds the first built-in module, KV Storage. KV Storage solved performance issue that localStorage had, and inheriting the benefits of simple APIs.

  • In Chrome 74, KV Storage can be enabled with the chrome://flags/#enable-experimental-web-platform-features flag.
  • See KV Storage demos from “Built-in Modules Demo” page.

KV Storage has similar APIs as the Map object. Strings and serializable data types can be used as key value. It returns a Promise or Async iterators, which are treated asynchronously.

The two named exports are “storage” and “StorageArea”.

  • storage: Is an instance of the StorageArea class with the name default(the default storage database is ‘kv-storage:default’).
  • StorageArea: Is provided for cases where additional isolation is needed (e.g. a third-party library that stores data and wants to avoid conflicts with data stored via the default storage instance). StorageArea data is stored in an IndexedDB database with the name kv-storage:${name}, where name is the name of the StorageArea instance.
import {storage} from "std:kv-storage";

const main = async () => {  
  const oldPreferences = await storage.get("preferences");

  document.querySelector("form")
    .addEventListener("submit", async () => {
       const newPreferences = Object.assign({}, oldPreferences, {
         // Updated preferences go here...
       });

       await storage.set("preferences", newPreferences);
  });
};
main();
Import maps

Import maps proposal allows control over what URLs get fetched by JavaScript import statements and import() expressions, and allows this mapping to be reused in non-import contexts.

Import maps provide Polyfill and fallback for built-in modules, enabling them to map currently unavailable module identifiers to URLs(see “Import Maps v0.5 Implementation Design Doc”).

For example, KV Storage, a built-in module, is currently available only in Chrome. In a supported browser, you can load without issue, but for those without the support, you need load polyfill of KV Storage instead.

The following example shows how to use Import maps. Define map for module and use the key URL value for import statement.

In the browsers without support, the import URL is recognized and processed as a normal import URL. If has supports, they will flow according to the mapping information.

<!-- The import map is inlined into your page -->  
<script type="importmap">  
{
  "imports": {
    "/path/to/kv-storage-polyfill.mjs": [
       "std:kv-storage",  // if has native support
       "/path/to/kv-storage-polyfill.mjs" // otherwise load polyfill
    ]
  }
}
</script>
<!-- Then any module scripts with import statements use the above map -->  
<script type="module">  
  import {storage} from '/path/to/kv-storage-polyfill.mjs';
// Use `storage` ...
</script>

Closing

JavaScript is still making steady changes. It is proving that it wasn’t a coincidence being the most popular language for many years. The coverage of new ECMAScript releases for browsers and Node.js is also constantly increasing, and even for some proposals before its completions.

Checkout “ECMAScript compatibility table” for the coverage.
The version of Node.js that supports the ECMAScript specification is as follows (see “Node.js ES2015/ES6, ES2016 and ES2017 support”).
 - ES2017: v9.11.2
 - ES2018: v10.3.0
 - ES2019: v12.0.0
Continued development through transparent and robust standardization process makes JavaScript reliable and strong.

Let’s journey continues for all!.

Discussion

pic
Editor guide