ECMSAScript 2020 is the latest update to the ECMA-262 scripting language standard. However, the update from ES5 (2009) to ES6 (2015) took 6 years to develop, offering a substantial amount of new features that made JavaScript feel almost like a complete new language. Thus, it’s important to review some of these features in ES6 so that you can continue your journey of JavaScript mastery.

JavaScript (ECMAScript) is an ever-evolving standard implemented by many vendors across multiple platforms. ES6 (ECMAScript 2015) was a large release which took six years to finalize. A new annual release process has been formulated to streamline the process and add features quicker. ECMAScript 2020 was released in June 2020 and is the latest iteration at the time of writing.

The major differences are summed up as follows:

  • Arrow Function
  • Object and Array Manipulation
  • Template Literal (`)
  • Async Functions (Promises vs Callbacks)
  • Exporting & Importing Modules
  • Block Scoping (let and const)

Arrow Function

Arrow functions provide two features: lexical scoping of the this keyword and less ceremony when defining an anonymous function.

Lexical Scoping of this keyword

Before ES6, the this keyword was bound to the parent. This worked well if you only needed access to the parent from within an object’s function.

var dog = {
   name: 'Spot',
   getName: function() {
       console.log(this.name); // Spot
   }
};

However, what if you had an object function that contained a function?

var dog = {
name: 'Spot',
tasks: [ 'eat food', 'chew on toy' ],
getTasks: function() {
    this.tasks.forEach(function(task) {
        console.log(this.name + ' will have to ' + task + ' today');
    });
}
};

// --Output--
// will have to eat food today
// will have to chew on toy today

Accessing this.name in the inner function doesn’t give us what we think it should. This is because the this keyword is bound to the owner of the function it is in. When it’s inside of an object’s method, it references the object. When it is either stand alone or within another method in the object, it will always be bound to the window/global object.

There were quirky ways to get around this in ES5 such as binding this to the method and/or assigning a new variable to the value of this that referenced the parent object. However, in ES6 you can now arrow functions that uses lexical scoping which pretty much means this refers to it’s current surrounding scope and no further. Thus, the inner function in the previous example will only bind to the inner function and not the object’s method or the object itself.

// ES5
var dog = {
   name: 'Spot',
   tasks: [ 'eat food', 'chew on toy' ],
   getTasks: function() {
       var _this = this;
       this.tasks.forEach(function(task) {
           console.log(_this.name + ' will have to ' + task + ' today');
       });
   }
};

// --Output--
// Spot will have to eat food today
// Spot will have to chew on toy today

// ES6
var dog = {
   name: 'Spot',
   tasks: [ 'eat food', 'chew on toy' ],
   getTasks: function() {
       this.tasks.forEach((task) => {
           console.log(this.name + ' will have to ' + task + ' today');
       });
   }
};

// --Output--
// Spot will have to eat food today
// Spot will have to chew on toy today

Function Declaration

You don’t need to use the function keyword to define a function nor do you need to use the return keyword to return a computed value. You can also remove the parenthesis around the property if there is only a single property.

// ES5
function square(num) {
   return num * num;
}

// ES6
const square = num => num * num;

Object and Array Manipulation

There are multiple new features that make working with objects and arrays much more pleasant.

Destructuring

You can now easily extract values out of an object and with fewer lines.

// ES5
var user = { id: 123, name: 'Jack', age: 35 };
var id = user.id;
var name = user.name;
var age = user.age;

// ES6
var user = { id: 123, name: 'Jack', age: 35 };
var {id, name, age} = user;

Spread Operator (…)

The spread operator takes an array or an object and expands it into a set of its individual items. For objects, this will allow you to do the what the Object.assign function does and copy one object’s values into another object. For arrays, this will allow you to break apart the values of the array into a set and do interesting things like merge arrays together.

// ES5
var object1 = { a: 1, b: 2 };
var object2 = { c: 3};
var object3 = Object.assign(object1, object2);

// ES6
var object1 = { a: 1, b: 2 };
var object2 = {  …object1, c: 3 };

// ES5
var arr1 = [ 1, 2, 3 ];
var arr2 = [ 3, 4, 5 ];
var arr3 = arr1.concat(arr2);

// ES6
var arr1 = [ 1, 2, 3 ];
var arr2 = [ 3, 4, 5 ];
var arr3 [ …arr1, arr2 ];

Object Definition

We can now more easily assign values to an object if the property names are the same in the definition.

// ES5
var a = 1;
var b = 2;
var c = 3;
var object1 = { a: a, b:b, c:c };

// ES6
var a = 1;
var b = 2;
var c= 3;
var object1 = { a, b, c };

Template literal (`)

Another bit of syntactical sugar is template literals that allow for embedded resources by using the back-tick (`) to enclose a string. Essentially, it allows you to easily create multi-line strings and string interpolation features.

// ES5
console.log('This is line one\n'  +
'and this is line two');

// --Output--
// This is line one
// and this is line two

// ES6
console.log(`This is line one`
and this is line two');

// --Output--
// This is line one
// and this is line two

// ES5
var a = 2;
var b = 3;
console.log(a + " + " + b + " = " + (a+ b)); // 2 + 3 = 5

// ES6
var a = 2;
var b = 3;
console.log(`${a} + ${b} = ${a + b)}`); // 2 + 3 = 5

Async Functions (Promises vs Callbacks)

Numerous times we need to make a non-blocking call to an API and execute some operation after the call returns with data. In ES5, we did this using callback functions which often led to “callback hell”. In ES6, Promises were introduced to provide a native way to handle asynchronous calls.

ES6 promises represent a value that we can handle at some point in the future. Some advantages over callbacks are,

  1. Improved readability when coordinating multiple asynchronous request (No more callback hell)
  2. Single channel for errors which provides better error-handling.
  3. Better control flow of asynchronous logic
// ES5
function getMessageES5(msg, name, timeout, cb) {
setTimeout(function () {
   cb(msg + " Hello " + name);
}, timeout);
}

getMessageES5("", "Jack", 300, function(msg) {
 getMessageES5(msg, "Jill", 500, function(msg) {
   getMessageES5(msg, "Jane", 200, function(msg) {
     console.log("After 1000ms, all calls returns in ES5 - " + msg);
   });
 });
});

// ---Output---
//  After 1000ms, all calls returns in ES5 -  Hello Jack Hello Jill Hello Jane

// ES6
function getMessageES6(msg, name, timeout) { 
   return new Promise((resolve, reject) => {
     setTimeout(function() {
       resolve(msg + " Hello " + name);
     }, timeout)
   })
}

getMessageES6("", "Jack", 300)
 .then(function(msg) {
   return getMessageES6(msg, "Jill", 500);
}).then(function(msg) {
   return getMessageES6(msg, "Jane", 200);
}).then(function(msg) {
   console.log("After 1000ms, all calls returned in ES6 - " + msg);
});

// ---Output---
//  After 1000ms, all calls returns in ES5 -  Hello Jack Hello Jill Hello Jane

Module export and imports

The syntax for exporting and importing modules changed completely with ES6.

Exporting Modules

// ES5
class Users { getUsers() { console.log('Getting users'); } }
module.exports = Users;

// ES6
class Users { getUsers() { console.log('Getting users'); } }
export default Users;

Importing Modules

// ES5
var Users = require('./Users');

// ES6
import Users from './Users';

Block Scoping (let and const)

Before ES6, developers only used the var keyword to create variables. ES6 introduced two new ways, let and const, that has different behaviors from var in terms of how they are scoped.

  • var is function scoped which means it’s available within the function that uses it.
  • let is block scope which means it is only available inside the “block” that it was created in as well as any nested block.
  • const is exactly the same as let, except once you assign a value to a const variable, you can’t reassign it to a new value.
// ES5 - var
var a = 0;
if(true) { 
var a = 1;
}
console.log(a) // 1

// ES6 - let
let a=0;
if(true) {
 let a=1;
}
console.log(a) // 0

// ES6 - const
const email = 'jack@gmail.com'
email = 'jill@gmail.com'  // TypeError: Assignment to constant variable