JavaScriptT Light_870x220

A lot of cool and interesting features are added in the JavaScript specifications every year. We’ll take a comprehensive look at the new features in JavaScript and their respective browser support.

The first popular graphical browser was released in 1993. Brenden Eich wrote the first version of JavaScript (then called LiveScript) in just 10 days in 1995 for Netscape Communications. Microsoft reverse-engineered the implementation of JavaScript and built JScript for Internet Explorer 3. The implementation of JavaScript and JScript was considerably different, so the developers faced a tough time building websites for the different browsers. This was an obstacle to the widespread take-up of JavaScript on the web.

Netscape submitted JavaScript to the Ecma International committee to standardize the different features of the language for different browser vendors. The first official edition of the language specification was released in 1997 and came to be known as ECMAScript. Since then, a lot of features have been added in JavaScript to compete with other programming languages. The browser vendors have significantly improved the performance of the JavaScript engine to help power up modern web applications.

The use of JavaScript is not just limited to web applications. It is being widely used in building command-line tools and server-side stuff using Node.js, desktop applications using Electron, cross-platform mobile applications using React Native and low-cost IOT devices. JavaScript is one of the most loved and popular languages in the developer community.

A lot of cool and interesting features are added in the JavaScript specifications every year. In this article, we’ll look at the new features added in JavaScript and their respective browser support. This is a comprehensive list of the features and enhancements done to improve the performance of the real-world applications, as announced in Google I/O 2019.

Real-World Performance Improvements

Parsing & Compilation of JavaScript Code

The JavaScript code is parsed and then compiled by the JavaScript engine to generate optimized machine code. The parsing speed has been doubled since Chrome version 61 to 75 as measured on the real-world applications.

chart showing 2x parsing speed results

When the browser encounters the JavaScript code, it blocks the main thread and gets busy in the process of parsing, compiling and interpreting this code. The V8 team has managed to pull off 40% of the parsing and compilation work from the main thread, and now this work is being done in another thread in parallel. This would significantly improve the start-up performance for web applications.

Side-Note: If you’re interested in reading on the internal process of parsing and compilation, head over to these links.

Promises

Promises got 11 times faster between Node.js version 7 and version 12, while promise.all in particular got up to 13 times faster between Chrome 54 and Chrome 75.

Memory Consumption

The memory consumption is reduced by 20% as measured on Android Go devices running real-world web applications.

Implementation of the JavaScript features announced in Google I/O 2018

Some of the JavaScript features were announced in 2018, but they weren’t supported in all the modern browsers. These features are now implemented in all the modern browsers, and you can use them in your applications without using any polyfill. Let’s take a quick look at these features:

Async Iterators and Generators

An iterator is basically a pointer for traversing the elements of a data structure sequentially. As of now, the iterable data structures are Arrays, Strings, Maps, and Sets. We can implement iteration on these data structures using the key Symbol.iterator. The next method defined in the iterator protocol returns an object with two properties:

value - Value of the next item in the sequence
done - It indicates the end of the sequence and returns true for the last value

We can asynchronously use Iterators and Generators for reading on-going streams of data. Let’s see this with the help of an example:

async function getResponseSize (url) {
    const response = await fetch(url)
    const reader = response.body.getReader()
    let total = 0
    while (true) {
        const { done, value } = await reader.read()
        if (done) return total
        total += value.length
    }

}

This example is taken from Google I/O 2018. Here, we’re calculating the total number of chunks in a stream. If you’re already familiar with the async-await syntax, this code should look familiar to you. But let me still put this into words to make things more clear. The code after await waits until the response of the fetch URL is received. We then extract data into chunks and keep adding the length of the chunk in the variable total. The while loop runs until we reach the end of the stream. This is a lot of boilerplate to manually wait for each chunk and then evaluate the end of the stream. The new JavaScript is less verbose and looks a lot cleaner.

async function getResponseSize (url) {
    const response = await fetch(url)
    let total = 0
    for await(const chunk of response.body) {
        total += chunk.length
    }
    return total
}

The for-await-of loop does the work of fetching the data from chunks and would terminate the process automatically once we reach the end of the stream. We can use the same syntax in Node.js as well instead of writing callbacks for data and end events.

finally in Promises

We can now use finally in promises. The code inside the finally block would be executed once the promise either gets resolved or rejected. Let’s look at its syntax:

function getData (url) {
    fetch (url)
        .then (response => {
            // ... doing some work on success callback
        })
        .catch (error => {
            // ... busy catching errors
        })
        .finally ( () => {
            // ... doing the common stuff like hiding the spinner or a popup irrespective of the success or error
        })
}

The finally block can also be added with the async await functions like so:

async function getData (url) {
    try {
        const response = await fetch(url)
        // ... doing some work on success callback
    } catch (error) {
        // ... busy catching errors
    } finally () {
        // ... doing the common stuff like hiding the spinner or a popup irrespective of the success or error
    }
}

Optional catch Binding

You can now write catch blocks without passing it any argument as:

try {
    doSomethingThatMightThrow()
} catch {
    handleException()
}

Please note the difference between the catch syntax from above and the one that we’ve been using till now. The less-often used parenthesis are now optional!

Trimming String From Only the Start or the End Side

The commonly used function for trimming the white spaces in a string is trim. However, there may be cases where we’ve to remove the whitespaces from only one end start or the end. This is now possible using the trimStart and the trimEnd methods. Let’s see this in action with the help of an example:

const str = `   Awesome JavaScript  `
str.trimStart() // `Awesome JavaScript  `
str.trimEnd() // `  Awesome JavaScript`

Spread and Rest Operators in Objects

We’ve been using the awesome spread and rest operators in Arrays, and the good news is that these operators are now supported in objects too.

let obj1 = {
	name: "Ronald Weasley",
	role: "student",
	house: "Gryffindor",
	school: "Hogwarts School of Witchcraft and Wizardry",
	alias: "Ron",
	wand: "Willow, 14\", unicorn hair tail"
}
let obj2 = {
    boggart: "Aragog",
	patronus: "jack russell terrier",
	ministryOfMagic: false,
	orderOfThePhoenix: true,
	dumbledoresArmy: true,
	deathEater: false,
	bloodStatus: "pure-blood",
	species: "human"
}

let ronObj = { ...obj1, ...obj2 } // Creating deep copies of the objects `obj1` and `obj2` using the spread operator.

let { name, role, house, ...rest } = ronObj

Notice how the objects obj1 and obj2 are concatenated to form ronObj. The spread operator is used for deep cloning these objects. The rest object holds all the other keys except name, role and house.

These are a bunch of pretty useful features in JavaScript and are supported in all the modern browsers. Let’s now look at the new features that are announced in Google I/O 2019.

New JavaScript Features from Google I/O 2019

list of new javascript features

These are the new JavaScript features announced in Google I/O 2019. Let’s look at each of them.

Class Fields

There are two enhancements related to the class fields. The first one is getting rid of the unnecessary constructor boilerplate. Let’s look at a simple class that implements the functionality of a Sorting Hat and put students in one of the four houses: ‘Gryffindor’, ‘Ravenclaw’, ‘Slytherin’ and ‘Hufflepuff’:

class SortingHat {
    constructor () {
        this._house = ''
    }

    get value () {
        console.log("You belong to")
        return this._house
    }

    sortHouse () {
        const houses = ['Gryffindor', 'Ravenclaw', 'Slytherin', 'Hufflepuff']
        const randomIndex = Math.floor(Math.random()*houses.length)
        return houses[randomIndex]
    }
}

const sortedHouse = new SortingHat()
sortedHouse.sortHouse()
sortedHouse.value

/* Expected Output
You belong to
Gryffindor
*/

The constructor in the above class is used to instantiate the objects and assign a default value to _house field. We’ve defined one getter function, and the sortHouse function uses an intensive algorithm (Random logic looks pretty tough to crack!) to come up with the appropriate house. The underscore at the start of the variable name is a convention to write private fields. However, it is just a convention and not something that is enforced strictly. This means, any student can access the property _house and might change his house!

The first improvement in the above syntax is getting rid of the constructor and directly initializing the class field at the top-level, and the second biggest improvement is private members are actually private. This is not just a convention but enforcement, and the value of private fields outside of the class will be undefined. Let’s see the improvised SortingHat class:

class SortingHat {
    #house = '';
    get value () {
        console.log("You belong to")
        return this.#house
    }

    sortHouse () {
        const houses = ['Gryffindor', 'Ravenclaw', 'Slytherin', 'Hufflepuff']
        const randomIndex = Math.floor(Math.random()*houses.length)
        return houses[randomIndex]
    }
}

const sortedHouse = new SortingHat()
sortedHouse.sortHouse() // Returned value is Slytherin
sortedHouse.value // Syntax Error

You’ll get an error on sortedHouse.value, when you try to access the private field of the class.

The public and private fields are supported by Chrome and Node.js.

BigInt and Numeric Separators

You can now separate big numbers using underscore (_). This makes it easy to read them! It is difficult to find the order of the number 1000000000 at first sight. However, if we format it as 1_000_000_000, it is easy to find its order. And the good news is most of the popular browsers support this numeric separator.

Another new thing with respect to numbers is related to BigInt. JavaScript allows type flexibility to developers: the data types - int, long, double, etc. - are managed by the underlying engine and all that developers care about is var.

There is one serious problem in this implementation and that is:

1234567890123456789 * 123 // 151851850485185200000

The output of the above code doesn’t look correct! The last digit should definitely be 7 (3*9). We have crossed the limit of the biggest number that an engine can hold and hence the error. We can solve this problem by using BigInt as:

1234567890123456789n * 123n

Try the above code in your browser console and check the validity of the result!

Numeric separators are shipping in Chrome 75 and BigInts are available in Chrome, Firefox Nightly, Safari and Node.js.

New Methods flat and flatMap in Arrays

As the name suggests, the flat method is used to flatten the arrays.

let arr = [1, 2, 3, [4, 5]]
arr.flat() // [1, 2, 3, 4, 5]

The flat method does not modify the original array and can be used to flatten n-level nested arrays as well. Let’s flatten the 2-level nested array:

let arr = [1, 2, [3, [4, 5]]]
arr.flat() // [1, 2, 3, [4, 5]]
arr.flat(2) // [1, 2, 3, 4, 5]

When we pass depth as 2 to the flat method, the array gets flattened 2-level depth. The default value of the depth is 1. We can also pass Infinity to the flat method to recursively flatten until the original array contains no nested arrays.

let arr = [1, [2, [3, [4, [5]]]]]
arr.flat(Infinity) // [1, 2, 3, 4, 5]

The flatMap method first applies the mapping function to each element of an array and then flattens the result into a new array. Let’s look at the use of the flatMap method:

const arr = [1, 2, 3, 4]
const doubleElement = (x) => [x*2]
arr.flatMap(doubleElement) // [2, 4, 6, 8]

Each element of the array is doubled using the doubleElement method and then the result is flattened 1-level using flatMap.

The flat and flatMap methods are available in Chrome, Firefox, Safari and Node.js.

Object.fromEntries Method

The Object.fromEntries() method transforms a list of key-value pairs into an object. It performs the reverse of the Object.entries method. Let’s first look at the Object.entries method that generates a list of key-value pairs from an object.

const obj = {
    name: "Ronald Weasley",
	role: "student",
	house: "Gryffindor"
}
Object.entries(obj1)

/* Expected Output
[
    ["name", "Ronald Weasley"],
    ["role", "student"],
    ["house", "Gryffindor"]
]
*/

As it can be seen from the above code, the Object.entries method converts an object into an array of key-value pairs. The Object.fromEntries does the exact opposite!

const objMap = new Map([
    ["name", "Ronald Weasley"],
    ["role", "student"],
    ["house", "Gryffindor"]
])
Object.fromEntries(objMap)

/* Expected Output
{
    name: "Ronald Weasley",
    role: "student",
    house: "Gryffindor"
}
*/

Object.fromEntries is available in Chrome, Firefox, Safari and Node.js.

globalThis

The context of this changes as per the execution context, so it is not reliable to use this for accessing the global object.

On the web, we can access the global object using window, self or frames, and, in the web workers, we can access the global object using the self keyword. In Node.js, the global object can be accessed by using the global keyword. The only reliable way to access the global this is:

var getGlobal = function () {
  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')
};

var globals = getGlobal()

if (typeof globals.setTimeout !== 'function') {
  // no setTimeout in this environment!
}

That’s a lot of boilerplate code to find the correct value of the global object. The globalThis is here to clear all the confusion! It will always refer to the global object, irrespective of the environment.

globalThis is now available in Chrome, Firefox, Safari, and Node.js.

V8 Now Implements Stable Sort

There was an open bug related to the stable sort in V8. Let’s consider an example to understand the problem first:

let obj = [
    {
        name: 'Rey',
        age: 23
    },
    {
        name: 'Rohan',
        age: 34
    },
    {
        name: 'Robert',
        age: 23
    }
]

If we were to sort the above object with respect to the age property in ascending order, the sequence of elements with the same age value was not fixed in the previous version of sorting algorithm. Sometimes, Rey would come before Robert and sometimes it could be the opposite. The sequence was not fixed! But this is corrected now, and you can use sort without worrying about the sequence of the elements.

Internationalization APIs

V8 has added support for some of the cool Intl APIs that provide locale-specific functionalities like date formatting, number formatting, plural forms, and collation. These APIs can be used to get/format information of many languages. The language-specific key is provided as the first argument in the constructor.

Let’s look at all these APIs in detail:

Intl.RelativeTimeFormat

The Intl.RelativeTimeFormat can generate human-friendly phrases for relative timestamps such as:

const relativeTF = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
relativeTF.format(-1, 'day') // yesterday
relativeTF.format(0, 'day') // today
relativeTF.format(1, 'day') // tomorrow
relativeTF.format(-1, 'week') // last week
relativeTF.format(0, 'week') // this week
relativeTF.format(1, 'week') // next week

The Intl.RelativeTimeFormat constructor takes in two optional parameters. The first one is used for specifying the locale information, and the second one takes in an options object. In our case, the numeric property is set as auto, which tells the formatter to use phrases like yesterday instead of 1 day ago. You can use this for other time units as well, like quarters, months, weeks, days, hours, minutes and seconds.

Now with the native locale-specific APIs in JavaScript, we can ship web applications without adding hundreds of kilobytes of locale-specific libraries.

Intl.ListFormat

This API is used for joining the elements of a list using either a disjunction or a conjunction.

const listFormatter = new Intl.ListFormat('en')
listFormatter.format(['John', 'Sushi', 'Mary', 'Elizabeth']) // John, Sushi, Mary and Elizabeth

Notice how it generates a nice string using all the elements of an array. The default type is a conjunction and hence the use of and. Below is an example of using it as a disjunction:

const listFormatter = new Intl.ListFormat('en', { type: 'disjunction' })
listFormatter.format(['Pizza', 'Pasta', 'Burger']) // Pizza, Pasta or Burger

Intl.DateTimeFormat

This API is used for formatting date ranges as below:

const dateFormatter = new Intl.DateTimeFormat('en', {
    year: 'numeric',
    month: 'short',
    day: 'numeric'
})
const startDate = new Date(start) // Mar 9 2019
const endDate = new Date(end) // Mar 12 2019

dateFormatter.formatRange(startDate, endDate) // Mar 9 - 12 2019

Intl.NumberFormat

const numberFormatter = new Intl.NumberFormat('en')
numberFormatter.format(1000000000) // 1, 000, 000, 000

This API formats the number as per the locale. For example, for en, the separator is , and for fr, it is blank space. It can also format BigInts.

allSettled and any Methods on a Promise

Currently, there are two combinators in JavaScript: promise.all and promise.race. Let’s take a look at these two methods:

promise.all: It is useful for aggregating the results of multiple promises. It takes an iterable as an argument. It returns a single promise that resolves when all of the promises passed in an iterable are resolved. If any of the promise is rejected, it returns with the value of the promise that is rejected. promise.all is useful when you want to send information to multiple services asynchronously on the invocation of an event. To maintain consistency between these services, you’d reject the promise even if any one of the promises in an iterable (promises to send information) is rejected.

promise.race: It takes in an iterable as an argument and returns a promise as soon as one of the promises in an iterable fulfills or rejects. This is specifically used when you’re interested in knowing which promise fails or succeeds at the earliest.

The two new promise combinators were announced in I/O 2019: promise.allSettled and promise.any. Let’s understand what these methods are for:

promise.allSettled: This returns a promise only when all of the promises in an iterable are settled; they’re either resolved or rejected. In this case, you don’t really care about the outcome of a promise. It is supported in Chrome 76 and Firefox Nightly.

promise.any: This returns a promise when one of the promises is resolved. This is different than promise.race, which returns when a promise is resolved or rejected. It is still going through the standardization process and is not supported in any browser yet.

WeakRef

The WeakRef is still going through the standardization process and is available behind a flag in V8. This is an interesting feature so I would like to cover it in this article.

In objects, keys are usually strings. Map and Set in JavaScript are used for storing key-value pairs, and here the key can be of any data type, including the object.

There is one problem when we store objects as keys in Map and Set data structures. If any of the objects is set to null, its reference is not garbage collected from these data structures, and this causes memory leaks. Objects are strongly held in these data structures.

The only way to weakly reference an object is by using WeakMap and WeakSet. This solves our above problem! But WeakMap and WeakSet are designed in a way that they can only accept objects as keys. Let’s consider one example from the I/O presentation where the use-case is to store images in a cache and clear them once they are not referenced anywhere:

const cache = new Map()

function getImageCached (name) {
    if (cache.has(name)) return cache.get(name)
    const image = performSomeExpensiveOperation(name)
    cache.set(name, image)
    return image
}

Please note: We’re using Maps here to store image names and data because WeakMap is not used for storing strings as keys.

The above function getImageCached fetches the image using the function performSomeExpensiveOperation and then stores it in a cache. We’ll fetch this image from the cache on subsequent requests. As we have seen earlier, Maps hold on to their keys and values strongly, so these images can be never be garbage collected and this would lead to increased memory consumption.

Here’s the solution - WeakRef. The new proposal solves the problem of memory leaks by creating a weak reference to an image and storing that in the cache instead of storing the image itself. The garbage collector can clear these image references when the memory consumption has reached a certain limit.

Here’s the solution code, again from the example mentioned in I/O:

const cache = new Map()

function getImageCached (name) {
    let ref = cache.get(name)
    if (ref !== undefined) {
        const deref = ref.deref()
        if (deref !== undefined) return deref
    }
    const image = performSomeExpensiveOperation(name)
    ref = new WeakRef(image)
    cache.set(name, ref)
    return image
}

Notice how a weak reference to an image is created using the WeakRef. Another problem with the name code is keys of the cache will not be garbage collected. The good news is we have a solution to this problem as well!

const finalizationGroup = new FinalizationGroup( iterator => {
    for (const name of iterator) {
        const ref = cache.get(name)
        if (ref !== undefined and ref.deref() === undefined) {
            cache.delete(name)
        }
    }
})

The FinalizationGroup group is used for registering a callback to remove the image names as well. Once the images are removed from the cache, this function will be invoked and it will do the work of removing the image names from the cache. You’ll have to add the below code in the getImageCached method after the line where the image is getting added to the cache. This is for registering the FinalizationGroup API:

finalizationGroup.register(image, name)

Conclusion

I hope you got an overview of all the new things happening in JavaScript. Some of the things mentioned above are not supported in all the browsers, but you can always use a polyfill. Have fun learning JavaScript!


This blog has been brought to you by Kendo UI

Want to learn more about creating great web apps? It all starts out with Kendo UI - the complete UI component library that allows you to quickly build high-quality, responsive apps. It includes everything you need, from grids and charts to dropdowns and gauges.

KendoJSft


Ankita Masand
About the Author

Ankita Masand

A Software engineer trying to make sense of every line of code she writes, Ankita is a JavaScript enthusiast and adores its weird parts. She’s also an obsessed developer, highly focused on the performance of web applications. When she’s not writing JavaScript, you’ll find her engaged in understanding the intricacies of deep neural networks.

Related Posts

Comments

Comments are disabled in preview mode.