ES6 Refresher | Smartcodehub

javascript Jul 28, 2019

The ECMAScript committee, which defines the specification for the JavaScript language itself, released a new specification called ECMAScript 6/ECMAScript 2015 in June 2015. The new standard, called ES6 for short, was a major revision of the JavaScript programming language and added a number of new paradigms intended to make development of JavaScript programs easier.

While ECMAScript defines the specification for the JavaScript language, the actual implementation of the language is dependent on the browser vendors and the maintainers of the various JavaScript engines. ES6 by itself is only a guideline, and because the browser vendors each have their own timeline for implementing new language features, the JavaScript language and the JavaScript implementations diverged slightly. Features defined by ES6, such as classes, were not available in the major browsers, but developers wanted to use them anyway.

Enter Babel, the JavaScript transpiler. Babel can read and parse different JavaScript flavors (such as ES6, ES7, ES8, and React JSX) and convert it or compile it into browser-standard ES5. Even today, the entirety of ES6 has not yet been implemented by the browser vendors, so Babel remains an essential tool for developers wishing to write ES6 code.

Object literals

ES6 makes some improvements to object literals. There are several improvements, but the one you'll see most is the implicit naming of object properties. In ES5 it would be as follows:

var name = ‘Jhon’;
var title = ‘Author’;
var object = {name: name, title: title};

In ES6, if the property name and the variable name are the same as the preceding one, you can simplify it to the following:

const name = ‘Jhon’;
const title = ‘Author’;
const object = {name, title};

Additionally, ES6 introduces the object spread operator, which simplifies shallow object merges. For instance, take a look at the following code in ES5:

function combinePreferences(userPreferences) {
 var defaultPreferences = {size: ‘large’, mode: ‘view’};
 return Object.assign({}, defaultPreferences, userPreferences);
}

The preceding code will create a new object from defaultPreferences, and merge in properties from userPreferences. Passing an empty object to the Object.assign instance first parameter ensures that we create a new object rather than overwriting defaultPreferences (which isn't an issue in the preceding example, but is an issue in real-life use cases).

And now, let's take a look at the same in ES6:

function combinePreferences(userPreferences) {
 var defaultPreferences = {size: ‘large’, mode: ‘view’};
 return {...defaultPreferences, ...userPreferences};
}

This approach does the same as the ES5 example, but is quicker and easier to read in my opinion than the Object.assign method. Developers familiar with React and Redux, for instance, often use the object spread operator when managing reducer state operations.

Let and const

In ES5 JavaScript, we use the var keyword to define variables. In most cases, var can simply be replaced with let, with the major difference between the two constructs being the scoping of the variable with respect to blocks. The following example from MDN web docs, or previously Mozilla Developer Network , demonstrates the subtle difference between the two:

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // same variable!
    console.log(x);  // 2
  }
  console.log(x);  // 2
 }
 
 
 function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // different variable
    console.log(x);  // 2
  }
  console.log(x);  // 1
 }

So, while you must use additional caution in cases like the preceding one, in most cases you can simply replace var with let.

The const keyword, unlike let, defines a variable as a constant; that is, you cannot reassign a variable initialized with const at a later date. For example, the following code causes an error with a message similar to invalid assignment to const a:

const a = 1;
a = 2;

On the other hand the same code, using var or let to define a, would run successfully.

The following code will run successfully:

const obj = {};
obj.name = ‘My Object’;

However, attempting to redefine objects such as in obj = {name: “other object”} would cause an error.

I find that in most programming contexts, const is typically more appropriate than let, as most variables you use never need to be redefined. My recommendation is to use const as much as you can, and use let only when you have a reason to redefine the variable later.

Classes

One very welcome change in ES6 is the addition of classes and class inheritance. Previously, object-oriented programming in JavaScript required prototypical inheritance, which many developers found unintuitive, like the following ES5 example:

var Automobile = function(weight, speed) {
   this.weight = weight;
   this.speed = speed;
}
Automobile.prototype.accelerate = function(extraSpeed) {
   this.speed += extraSpeed;
}
var RaceCar = function (weight, speed, boost) {
   Automobile.call(this, weight, speed);
   this.boost = boost;
}
RaceCar.prototype = Object.create(Automobile.prototype);
RaceCar.prototype.constructor = RaceCar;
RaceCar.prototype.accelerate = function(extraSpeed) {
  this.speed += extraSpeed + this.boost;
}

In the preceding code, extending an object requires calling the parent class in the child's constructor function, creating a clone of the parent's prototype object, and overriding the parent's prototype constructor with the child's prototype constructor. These steps were seen as unintuitive and burdensome by most developers.

Using ES6 classes, however, the code will look like this:

class Automobile {
 constructor(weight, speed) {
   this.weight = weight;
   this.speeed = speed;
 }
 accelerate(extraSpeed) {
   this.speed += extraSpeed;
 }
}
class RaceCar extends Automobile {
constructor(weight, speed, boost) {
   super(weight, speed);
   this.boost = boost;
 }
 accelerate(extraSpeed) {
   this.speed += extraSpeed + this.boost;
 }
}

The preceding syntax is more in line with what we'd expect from object-oriented programming, and also makes inheritance much simpler.

It's important to note that under the hood, ES6 classes still use JavaScript's prototypical inheritance paradigm. Classes are just syntactic sugar on top of the existing system, so there is no significant difference between these two approaches other than clean code.

Module imports

ES6 also defines a module import and export interface. With the older CommonJS approach, modules are exported using the modules.export construct, and modules are imported with the require(filename) function. The ES6 approach looks a little different. In one file, define and export a class, as shown in the following code:

Class Automobile {
…
}
export default Automobile

And in another file, import the class, as shown in the following code:

import Automobile from ‘./classes/automobile.js’;
const myCar = new Automobile();

At present, Babel compiles ES6 modules to the same format as CommonJS modules, so you can use either the ES6 modules syntax or the CommonJS modules syntax if you’re using Babel.

Arrow functions

One quirky, useful, but somewhat annoying aspect of ES5 JavaScript is its heavy use of callbacks that run asynchronously. You are probably intimately familiar with jQuery code that looks something like this:

$(“#link”).click(function() {
  var $self = $(this);
  doSomethingAsync(1000, function(resp) {
    $self.addClass(“wasFaded”);
    var processedItems = resp.map(function(item) {
      return processItem(item);
    });
    return shipItems(processedItems);
  });
});

We're forced to create a variable called $self because the original this context is lost in our inner anonymous function. We also have a lot of boilerplate and difficult-to-read code due to needing to create three separate anonymous functions.

Arrow function syntax is both syntactic sugar that helps us write anonymous functions with a shorter syntax, and also a functional update that preserves the context of this inside an arrow function.

For instance, the preceding code may be written in ES6 as follows:

$(“#link”).click(function() {
  dozsSomethingAsync(1000, resp => {
    $(this).addClass(“wasFaded”);
    const processedItems = resp.map(item => processItem(Item));
    return shipItems(processedItems);
  });
});

You can see in the preceding code that we no longer need a $self variable to preserve this, and our call to .map is much simpler, no longer requiring the function keyword, parentheses, curly braces, or a return statement.

Now let's look at some equivalent functions. Let's look at the following code:

const double = function(number) {
  return number * 2;
}

The preceding code would be similar to:

const double = number => number * 2;
// Is equal to:
const double = (number) => { return number * 2; }

In the aforementioned examples, we can omit the parentheses around the number parameter because the function only requires one parameter. If the function required two parameters, we would be required to add parentheses as in the next example. Additionally, if the body of our function only requires one line, we can omit the function body curly braces and omit the return statement.

Let's look at another equivalence, with multiple parameters, as shown in the following code:

const sorted = names.sort(function (a, b) {
  return a.localeCompare(b);
});

The preceding code would be similar to:

const sorted = names.sort((a, b) => a.localeCompare(b));

I find that arrow functions make themselves most useful in situations like the preceding one, when you're doing data transformations, especially where using Array.map, Array.filter, Array.reduce, and Array.sort calls with straightforward function bodies. Arrow functions are less useful in jQuery because of jQuery's tendency to give you data using the this context, which you don't receive with anonymous arrow functions.

Neeraj Dana

Experienced Software Engineer with a demonstrated history of working in the information technology and services industry. Skilled in Angular, React, React-Native, Vue js, Machine Learning