ES6/ECMAScript 2015 Features you should be using right now

JavaScript-ES6-ES2015-Features

Brief History

For years, many enterprise developers have doubted if JavaScript could serve as a workable programming language and no doubt, early JavaScript had its quirks. But all that has changed significantly now, all thanks to ES6 (also known as ECMAScript 2015). ES6 brought with it, many much-needed improvements and new features and is considered a significant move forward for JavaScript.

Even though ES6 was released back in 2015, the browsers took time to add support for the new specs. As of this writing, most major browsers support most ES6 features out-of-the-box. If you still want to check browser support for a specific feature, you can check it on MDN Web Docs. If you’re using Typescript, you don’t have to worry about browser support anyway since your code will eventually be transpiled to a lower ES version.

In this article, we’re going to see the key ES6/ECMAScript 2015 features/improvements. Alright then, let’s get started…

ES6 Features

  1. Arrow Functions
  2. Classes
  3. Template Literals / Template Strings
  4. Block-Scoped let and const keywords
  5. Enhanced Object Properties
  6. Default Parameters
  7. Rest Parameters
  8. Spread Operator
  9. Destructuring Assignment
  10. Iterator & for…of Operator
  11. Promises
  12. Modules
  13. Map/Set & WeakMap/WeakSet
  14. Symbol Type

1. ES6 Arrow Functions

Since you’ve been working with JavaScript, you probably already know that the this keyword refers to the containing object, in case of functions, the this keyword inside a function body refers to the function itself. You’d sometimes want the this keyword to refer to a consistent parent. Arrow functions allow you to do just that and also helps you reduce the code by a few lines in the process.

Arrow function expressions are a function shorthand that uses the arrow (=>) syntax. They support both statement block bodies as well as expression bodies which return the value of the expression. Unlike regular functions, arrows share the same lexical this as their surrounding code.

// Expression bodies
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);
var pairs = evens.map(v => ({even: v, odd: v + 1}));

// Statement bodies
nums.forEach(v => {
  if (v % 5 === 0) {
    fives.push(v);
  }
});

// Lexical this
var bob = {
  _name: "Bob",
  _friends: [],
  printFriends() {
    // this refers to the parent object `bob`
    this._friends.forEach(f => console.log(this._name + " knows " + f));
  }
}

2. ES6 Classes

If you love object-oriented programming (OOP), then you’ll love this feature. The new JavaScript class syntax brings you closer to the classic Object Oriented programming paradigm. However, the point to be noted is that JavaScript classes are primarily syntactical sugar over JavaScript’s existing prototype-based inheritance pattern. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.

In short, JavaScript does not actually support classes the same way as, say, Java does. Classes in JavaScript are in fact “special functions”, and just as you can define function expressions and function declarations, the class syntax has two components: class expressions and class declarations.

Class Definition

// Class Declaration
class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }
}

// OR

// Class Expression
let Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

// A new instance of this class can be created with
const rect = new Rectangle(100, 200);
console.log(rect._width); // Prints 100

Getters / Setters

Getters and Setters allow getting and setting values of class properties. A Setter function accepts the new value as parameter while a Getter does not accept any parameters and returns the current value of the class property.

class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }

  get width() {
    return this._width
  }

  set width(val) {
    this._width = val;
  }
}

// A new instance of this class can be created with
const rect = new Rectangle(100, 200);
console.log(rect.width); // Prints 100
rect.width = 200;
console.log(rect.width); // Prints 200

Static Methods

Static methods are called without instantiating their class and cannot be called through a class instance. Static methods are often used to create utility functions for an application. Generally speaking, any class method which does not have reference to this can be tagged as static method.

class Rectangle {
  constructor(width, height) {
    this._width = width;
    this._height = height;
  }

  area() {
    return this._width * this._height;
  }

  static printArea(area) {
    console.log('The area of the rectangle is ' + area);
  }
}

const rect = new Rectangle(100, 200);
const area = rect.area(); // non-static method invocation
Rectangle.printArea(area); // static method invocation

Inheritance

The extends keyword is used in class declarations or class expressions to create more intuitive, OOP-style and boilerplate-free inheritance.

class Rectangle extends Shape {
    constructor (id, x, y, width, height) {
        super(id, x, y)
        this.width  = width
        this.height = height
    }
}

3. Template Literals / Template Strings

Template strings provide syntactic sugar for constructing strings. Optionally, a tag can be added to allow the string construction to be customized, avoiding injection attacks or constructing higher level data structures from string contents.

// Basic literal string creation
`In JavaScript '\n' is a line-feed.`

// Multi-line strings
`In JavaScript this is
 not legal.`

// String interpolation
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// Construct an HTTP request 
// is used to interpret the replacements and construction
POST`http://foo.org/bar?a=${a}&b=${b}
     Content-Type: application/json
     X-Credentials: ${credentials}
     { "foo": ${foo},
       "bar": ${bar}}`(myOnReadyStateChangeHandler);

4. Block-Scoped let and const keywords

let and const provide block-scoped binding constructs. let is the new var. It declares a block scope local variable, optionally initializing it to a value. The value of a variable declared with let statement can be re-assigned. const, on the other hand, is single-assignment. The value of a const can’t be changed through reassignment, and it can’t be re-declared. Static restrictions prevent use before the assignment.

function f() {
  {
    let x;
    {
      // okay, block scoped name
      const x = "sneaky";

      // error, cannot re-assign const
      x = "foo";
    }
    // error, already declared in block
    let x = "inner";
  }
}

5. Enhanced Object Properties

Object literals are extended to support setting the prototype at construction, shorthand for foo: foo assignments, defining methods, making super calls, and computing property names with expressions. Together, these also bring object literals and class declarations closer together and let object-based design benefit from some of the same conveniences.

Property Shorthand

Very handy if you want to define an object who’s keys have the same name as the variables passed-in as properties, you can use the shorthand and simply pass the key name.

var x = 0, y = 0;
const obj = { x: x, y: y }; // old
const obj = { x, y }; // new

Computed (dynamic) property names

ES6 Computed property names allow you to dynamically create property names using an expression in brackets ( [ ] ) similar to property accessors.

// old way
var obj = {
  foo: "bar"
};
obj[ "baz" + quux() ] = 42;

// now possible
let obj = {
  foo: "bar",
  [ "baz" + quux() ]: 42
}

Method Properties

Allows method notation in object property definitions, for both regular functions and generator functions.

// old way
obj = {
  foo: function (a, b) {
    …
  },
  bar: function (x, y) {
    …
  },
  //  quux: no equivalent in ES5
  …
};


// now possible
obj = {
  foo(a, b) {
    …
  },
  bar(x, y) {
    …
  },
  * quux(x, y) {
    …
  }
}

6. Default Parameters

Default Parameters allow you to set default values for the function parameters if no value is passed or if undefined is passed. Default parameters are a great way to make your methods more organized, and in some cases even shorter. Overall, default function parameters help you focus more on the actual purpose of the method without the distraction of lots of default preparations and if statements.

// old way
function multiply(a, b) {
  if (b === undefined) {
    b = 1;
  }
  return a * b;
}

// New way - with default parameter
function multiply(a, b = 1) {
  return a * b;
}

console.log(multiply(5, 2));
// expected output: 10

console.log(multiply(5));
// expected output: 5

7. Rest Parameters

The rest parameter syntax allows us to represent an indefinite number of arguments as an array. Spread Operator helps bind trailing parameters to an array. It replaces the need for arguments and addresses common cases more directly.

function f(x, ...y) {
  // y is an Array ["hello", true]
  return x * y.length;
}
f(3, "hello", true) == 6

8. Spread Operator

The spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

function f(x, y, z) {
  return x + y + z;
}
// Pass each element of array as argument
f(...[1,2,3]) == 6

9. Destructuring Assignment

The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables. Destructuring allows binding using pattern matching, with support for matching arrays and objects. Destructuring is fail-soft, similar to standard object lookup foo["bar"], producing undefined values when not found.

Basic Example

// Old way
var tmp = getASTNode();
var op  = tmp.op;
var lhs = tmp.lhs;
var rhs = tmp.rhs;

// Now possible
var { op, lhs, rhs } = getASTNode();

More Examples

// list/array matching
var [a, , b] = [1,2,3];

// object matching
var { op: a, lhs: { op: b }, rhs: c } = getASTNode();

// object matching shorthand
// binds `op`, `lhs` and `rhs` in scope
var {op, lhs, rhs} = getASTNode();

// Can be used in parameter position
function g({name: x}) {
  console.log(x);
}
g({name: 5});

// Fail-soft destructuring
var [a] = [];
a === undefined;

// Fail-soft destructuring with defaults
var [a = 1] = [];
a === 1;

10. Iterator & for...of Operator

Adds support for the “iterable” protocol to allow objects to customize their iteration behavior. Additionally, adds support for the “iterator” protocol to produce a sequence of values (either finite or infinite). Finally, provide convenient of operator to iterate over all values of an iterable object.

The for...of statement  is the latest addition to the list of iteration controls in JavaScript that can create a loop iterating over iterable objects, including built-in StringArray, array-like objects (e.g., arguments or NodeList), TypedArrayMapSet, and user-defined iterables.

// Iterator
let fibonacci = {
  [Symbol.iterator]() {
    let pre = 0, cur = 1;
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        return { done: false, value: cur }
      }
    }
  }
}

// for...of
for (var n of fibonacci) {
  // truncate the sequence at 1000
  if (n > 1000)
    break;
  console.log(n);
}

11. ES6 Promises

Promises in ES6 are a library for asynchronous programming. A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation. Promises are used in many existing JavaScript libraries.

function timeout(duration = 0) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, duration);
    })
}

var p = timeout(1000).then(() => {
    return timeout(2000);
}).then(() => {
    throw new Error("hmm");
}).catch(err => {
    return Promise.all([timeout(100), timeout(200)]);
})

12. ES6 Modules

As you may know, there was no native modules support in JavaScript before ES6. People came up with AMD, RequireJS, CommonJS and other workarounds. Now there are modules with import and export operands. With ES6 there are now built-in modules with import and export operands. ES6 modules provide support for exporting/importing values from/to modules without polluting the global namespace.

// lib/math.js
export function sum(x, y) {
  return x + y;
}
export var pi = 3.141593;

// app.js
import * as math from "lib/math";
alert("2π = " + math.sum(math.pi, math.pi));

// otherApp.js
import {sum, pi} from "lib/math";
alert("2π = " + sum(pi, pi));

13. Map/Set & WeakMap/WeakSet

Provides efficient data structures for common algorithms.

The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.

The Set object lets you store unique values of any type, whether primitive values or object references.

The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced. The keys must be objects and the values can be arbitrary values.

The WeakSet object lets you store weakly held objects in a collection.

// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;

// Maps
var m = new Map();
m.set("hello", 42);
m.set(s, 34);
m.get(s) == 34;

// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined

// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });
// Because the added object has no other references, it will not be held in the set

14. ES6 Symbol Data Type

Symbols allow properties to be keyed by either string (as in ES5) or symbol. Symbols are a new primitive type. Symbols are unique and immutable data type that are to be used as an identifier for object properties. Symbol can have an optional description, but for debugging purposes only.

var MyClass = (function() {

  // module scoped symbol
  var key = Symbol("key");

  function MyClass(privateData) {
    this[key] = privateData;
  }

  MyClass.prototype = {
    doStuff: function() {
      ... this[key] ...
    }
  };

  return MyClass;
})();

var c = new MyClass("hello")
c["key"] === undefined

I hope you enjoyed the article. If you want some practice, you can use the sandbox for the learning process: https://es6console.com/. If you need more information, you can find it here:

You may also like: JavaScript Style Guide: Key takeaways from Google’s Style Guide

Be sure to follow us on the social media to get notified about the latest posts as soon as they’re published

Featured Image Credit: Photo by You X Ventures on Unsplash.

Leave a comment

Your email address will not be published. Required fields are marked *