JavaScript : Must Know Features (ECMAScript 2018)

Deepak Jalna Oomnarayanan
novice2pro
Published in
10 min readNov 22, 2018
Photo by Kobu Agency on Unsplash

This article is going to explain in detail about the comprehensive list of features included in the ECMAScript 2018 (ES2018) or ES9 if you prefer the old notation and also compare it with how these features were implemented before ES2018.

  • Spread Properties
  • Rest Properties
  • Promise.prototype.finally
  • Template Literal Revision
  • Asynchronous Iteration
  • RegExp Named Capture Group
  • RegExp Unicode Property Escapes
  • RegExp Lookbehind Assertions
  • RegExp s (dotAll) Flag

Spread Properties

The spread operator was initially introduced in ES2015(ES6) to be used in arrays not with ES2018 we can use the spread operators inside of the objects.

Consider the following example, you need to clone an existing object and add another property to the cloned object. Before ES2018 this can be done using the Object.assign() as shown below.

//Pre-ES2018 way
const age = {mark: 20, stephen: 18, kyle: 16};
const newAge = Object.assign({},age,{ben: 25});
console.log(newAge);
Console output (Pre-ES2018)

Now, with ES2018 we can use the spread operator with the three-dot (…) notation to do the same.


// ES2018
const age = {mark: 20, stephen: 18, kyle: 16};
const newAge = {
...age,
ben: 25
};
console.log(newAge);
Console output (ES2018)

You could also use the spread operator to clone objects (obj2 = { ...obj1 };), but be aware you only get shallow copies. If a property holds another object, the clone will refer to the same object.

Rest Properties

The Rest operator is has the same three-dot (…) notation which can be used in a slightly different way. For example, we have an object and we need to get out few of the properties and assign it to individual variables (which is also known as destructuring which can be done by destructuring assignment, which was introduced in ES2015) and also put the remaining properties in another object and this can be done using the lodash’s omit function as shown below.

// Pre-ES2018 way
const age = {
mark: 20,
stephen: 18,
kyle: 16,
ben: 25,
george: 21,
jim: 23
};
const {mark,stephen,kyle} = age;
const notRequired = _.omit(age, ['mark', 'stephen', 'kyle']);
console.log(notRequired);
Console output (Pre-ES2018)

Instead of using the lodash’s omit function the same can be done using the rest operator along with destructuring assignment.

//ES2018
const age = {
mark: 20,
stephen: 18,
kyle: 16,
ben: 25,
george: 21,
jim: 23
};
const {mark,stephen,kyle,...notRequired} = age;
console.log(notRequired);
Console Output (ES2018)

You can also use it to pass it to the function as shown below,

doSomething({
mark: 20,
stephen: 18,
kyle: 16
});

function doSomething({ mark, ...notRequired }) {
// mark = 20
// notRequired = { stephen: 18, kyle: 16 }
}

Promise.prototype.finally

A code-block with a Promise chain can either successfully execute and reach the final .then() block or cause an exception and reach the .catch() block, in both the cases you might want to run a set of statements regardless of the outcome. For example, these statements could be cleaning up the variables or closing the database connections, etc.

doSomething()
.then(() => {
// close connection here
})
.catch(err => {
console.log(err);
// close connection here
})

Before we had .finally() this was implemented by adding another .then() block after the .catch()

doSomething()
.then(doSomethingNew)
.catch(err => {
console.log(err);
}).then(() => {
// close the connection
})

Pitfall 1 : The above code block might not work as expected if an exception happens in the .catch() block, this will prevent the final .then() from executing

Pitfall 2 : If you didn’t have the .catch() and the error happened in the first .then() , then the final .then() will not be reached.

Pitfall 3 : If the first .then() has a return value then this value has to be manually passed through the final .then().

We finally have finally() in promises.

Instead of duplicating the final logic in both the blocks (.then() & .catch()), we can use the .finally() block. The .finally() gets called regardless of the outcome (this is similar to the try…catch…finally block).

let connection 
db.open()
.then((conn) => {
connection = conn
return connection.select({name: 'Jim'})
})
.catch(err => {
console.log(err);
})
.finally(() => {
connection.close()
})

If you use .finally() the return value from the .then() is automatically passed through.

Template Literal Revision

Template Literals were defined officially in ES2015 (ES6) specification which allows using multi-line strings and performing an expression interpolations easily. Also, on top of that, template literals could be attached with ‘tags’ which are just ordinary functions for manipulating the string. Lets look into it in detail.

Multi-line Strings

Template literals are enclosed by the back-ticks ` ` . Any newline characters inserted in the source are part of the template literal.

// before ES2015
console.log('string text line 1\n' + 'string text line 2');
// after ES2015
console.log(`string text line 1
string text line 2`);
// both of these statements will give same output.
// "string text line 1
// string text line 2"

Expression interpolation

With template literals, you are now able to make use of the syntactic substitutions which are more readable

// before ES2015
var a = 10;
var b = 5;
console.log('a + b is ' + (a + b) + ' and\n a - b is ' + (a - b) + '.');
// output
// "a + b is 15 and
// a - b is 5."
// after ES2015
var a = 10;
var b = 5;
console.log(`a+b is ${a + b} and
a-b is ${a - b}.`);
// output
// "a + b is 15 and
// a - b is 5."

Tagged templates

The advanced form of the template literals are the tagged templates. The first argument of a tag function contains an array of string values. The remaining arguments are related to the expressions. In the end, your tagged function can return your manipulated string as described in the below example.

The name of the function used for the tag anything.

var name = 'Jim';
var age = 28;
function myTag(strings, nameExp, ageExp) {
// strings[0] contains "My name is "
// strings[1] contains " , I am a "
var ageStr;
if (ageExp > 99){
ageStr = 'centenarian';
} else {
ageStr = 'youngster';
}
return `${strings[0]}${nameExp}${strings[1]}${ageStr}`;
}
var output = myTag`My name is ${ name } , I am a ${ age }`;console.log(output);// output
// "My name is Jim , I am a youngster"

The tagged function has a special raw property which is available in the first argument of the tagged template function which allows you to access the raw strings as they were entered without any processing of the escape sequences.

function tag(strings) {
console.log(strings[0]);
console.log(strings.raw[0]);
}
tag`string text line 1 \n string text line 2`;// output// "string text line 1
// string text line 2"
// "string text line 1 \n string text line 2"

As of ES2016 the tagged templates conforms to the rules of the escape sequences hence the following were the outcome,

  • \u starts a Unicode escape, which must look like \u{1F4A4} or \u004B.
  • \x starts a hex escape, which must look like \x4B.
  • \ plus digit starts an octal escape (such as \141). Octal escapes are forbidden in template literals and strict mode string literals.

This made it impossible to create certain strings such as the file path in windows c:\uuu\xxx\111. this means that a tagged template like this is problematic because as per the ECMAScript rules a parser looks for the valid Unicode escape sequences but finds a malformed syntax.

As of the revision to the tagged templates in ES2018, all the syntactic restrictions related to the escape sequences in the template literal has been removed to support the embedding of languages like LaTeX, etc., where the other escape sequences are quite common. However, the illegal escape sequences must still be represented in the ‘cooked’ representation and they will show up as undefined element in the ‘cooked’ array.

function latex(str) { 
console.log(str[0])
console.log(str.raw[0])
}

latex`\unicode`
// output
// undefined
// "\\unicode"

Note that the escape sequence restriction is only dropped from tagged templates and not from untagged template literals:

let bad = `bad escape sequence: \unicode`;

the above statement still causes a syntax error

Invalid escape sequence in template

Asynchronous Iteration

When you start using async/await functionality there might be a situation when you need to call an asynchronous function inside a synchronous loop.

For Example:

// Before Asynchronous Iteration
const promises = [
new Promise(resolve => setTimeout(resolve('One'), 1000)),
new Promise(resolve => setTimeout(resolve('Two'), 1000)),
new Promise(resolve => setTimeout(resolve('Three'), 1000))
];
async function test(){
for (const obj of promises){
console.log(obj);
}
}
test();//output - returns 3 promises
// [Object Promise]{}
// [Object Promise]{}
// [Object Promise]{}

The above code will not execute as expected because the loop themselves remain synchronous and will always complete before their inner asynchronous operations.

ES2018 introduces asynchronous iterations for these kind of situations. So, now the for … of loop becomes for … await … of loop to run the asynchronous operations in series. Consider the same example mentioned above,

// After Asynchronous Iteration
const promises = [
new Promise(resolve => setTimeout(resolve('One'), 1000)),
new Promise(resolve => setTimeout(resolve('Two'), 1000)),
new Promise(resolve => setTimeout(resolve('Three'), 1000))
];
async function test(){
for await (const obj of promises){
console.log(obj);
}
}
test();//output
// "One"
// "Two"
// "Three"

The cool thing about asynchronous iteration is that the loop waits for each Promise to get resolved before going to the next loop.

RegExp Named Capture Groups

Named Groups is a feature in RegExp which is already in use in other languages like Java, Python, etc. and it finally it comes to JavaScript. This features allows developers writing RegExp to provide names (identifiers) in the format(?<name>...) for different parts of the group in the RegExp. They can then use that name to grab whichever group they need with ease.

Each name should be unique and follow the grammar for ECMAScript Identifier Name.

Any named group that fails to match has its property set to undefined.

Named groups can be accessed from properties of a groups property of the regular expression result. Numbered references to the groups are also created, just as for non-named groups.

For example:

// before ES2018 
let re = /(\d{4})-(\d{2})-(\d{2})/u;
let result = re.exec('2018-11-21');
// result[0] = '2018-11-21';
// result[1] = '2018';
// result[2] = '11';
// result[3] = '21';
// result.input = '2018-11-21';
// after ES2018let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2018-11-21');
// result.groups.year === '2018';
// result.groups.month === '11';
// result.groups.day === '21';

// result[0] === '2018-11-21';
// result[1] === '2018';
// result[2] === '11';
// result[3] === '21';

A named group can also be accessed within a regular expression via the \k<name> construct which is also known as back reference.

For example:

//before ES2018
let re= /^([a-z]+)!(\1)$/;
re.test('abc!abc'); // true
re.test('abc!ab'); // false
//after ES2018
let re= /^(?<word>[a-z]+)!\k<word>$/;
re.test('abc!abc'); // true
re.test('abc!ab'); // false

Named groups can be referenced from the replacement value passed to String.prototype.replace too. If the value is a string, named groups can be accessed using the $<name> syntax.

For example:

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = '2018-11-21'.replace(re, '$<day>/$<month>/$<year>');
// result === '21/11/2018'

RegExp Unicode property escapes

Until ES2018, it isn’t possible to access the Unicode character properties natively in the regular expressions. ES2018 adds the Unicode property escapes by using the \p{…} in the regular expressions that have the u (unicode) flag set.

For Example:

const re = /\p{Script=Greek}/u;
re.test('π'); // true
const re = /\p{Script=Tamil}/u;
console.log(re.test('தமிழ்')); // true
const re = /\p{Emoji}/u;
console.log(re.test('❤️')); // true

We can use capital “P”(\P ) escape character instead of small p (\p ), to negate the matches.

RegExp lookbehind Assertions

JavaScript already supports lookahead assertions inside a regular expression. Which means, whatever comes next must match the assertion but nothing is captured, and the assertion isn’t included in the overall matched string.

For example:

// lookahead assertion (positive)const re = /aa(?=bb)/
// it matches the string 'aabb' but the overall matched string does // not include the 'bb'
const matchStr = re.exec('aabb')
console.log(matchStr[0]); // "aa"
const matchStr = re.exec('aab')
console.log(matchStr[0]); // null

A negative lookahead assertion means that what comes next must not match the assertion. For example:

// lookahead assertion (negative)const re= /aa(?!bb)/;
console.log(re.test('aabb')); // false
console.log(re.test('aab')); // true
console.log(re.test('aac')); // true

ES2018 introduces the lookbehind assertions which works exactly like the lookahead but in the opposite direction

// lookbehind assertion (positive)
const re= /(?<=aa)bb/;
// it matches the string 'aabb' but the overall matched string does // not include the 'aa'
const matchStr = re.exec('aabb')
console.log(matchStr[0]); // "bb"
const matchStr = re.exec('aab')
console.log(matchStr[0]); // null
// lookbehindassertion (negative)const re= /(?<!aa)bb/;
console.log(re.test('aabb')); // false
console.log(re.test('abb')); // true
console.log(re.test('cbb')); // true

Another Example:

// lookahead assertion
const reLookAhead = /\D(?=\d+)/
const matchStr = reLookAhead.exec('$456.12')
console.log(matchStr[0]); // $
// lookbehind assertion
const reLookBehind = /(?<=\D)\d+/
const matchStr = reLookBehind.exec('$456.12')
console.log(matchStr[0]); // 456

Regular Expression s (dotAll) Flag

Before ES2018, the dot . is supposed to match any single character but it doesn’t match the line terminators like \n, \r, \f etc. The s flag changes this behavior so line terminators are permitted.

For Example:

//Before ES2018
const re = /hello.world/
re.test('hello\nworld'); //false
//After ES2018
const re = /hello.world/s
re.test('hello\nworld'); //true

That’s pretty much all the features offered in ES2018! Happy Coding!

--

--