Chapter 15 ES6 Features

As discussed in Chapter 10, the ECMAScript specification for the JavaScript language has gone through several different versions, each of which adds new syntax and features to try and make the language more powerful or easier to work with. In 2015, a new version of the language was released—officially called “ECMAScript 2015”, most developers refer to it by the working name “ES6” (e.g., version 6 of the language).

This chapter introduces some of the most notable and useful features introduced in ES6—particularly those that will be utilized by the React framework (discussed in the following chapters).

ES6 is mostly supported by modern browsers, with the notable exception of Internet Explorer. However, the JavaScript interpreter in older browsers (and IE) won’t be ale to recognize the new syntax introduced in this version. Instead, you would need to covert that code into equivalent ES5 (or earlier) code, which can be understood. The easiest way to do this is with the Babel compiler, which will “transpile” JavaScript code from one version to another. The next chapter discusses how to perform this transpiling with React (spoiler: it’s automatically performed by provided build tools), but it is also possible to install and utilize the Babel compiler yourself.

15.1 Classes

While JavaScript is primarily a scripting and functional language, it does support a form of Object Oriented Programming like that used in the Java language. That is, we are able to define classes of data and methods that act on that data, and then instantiate those classes into objects that can be manipulated. ES6 introduces a new class syntax so that creating classes in JavaScript even looks like how you make classes in Java!

Why Classes?

The whole point of using classes in programming—whether Java or JavaScript—is to perform abstraction: we want to be able to encapsulate (“group”) together parts of our code so we can talk about it at a higher level. So rather than needing to think about the program purely in terms of Numbers, Strings, and Arrays, we can think about it in terms of Dogs, Cats or Persons.

In particular, classes encapsulate two things:

  1. The data (variables) that describe the thing. These are known as attributes, fields or instance variables (variables that below to a particular instance, or example, of the class). For example, we might talk about a Person object’s name (a String), age (a Number), and Halloween haul (an array of candy).

  2. The behaviors (functions) that operate on, utilize, or change that data. These are known as methods (technically instance methods, since they operate on a particular instance of the class). For example, a Person may be able to sayHello(), trickOrTreat(), or eatCandy().

In JavaScript, an Object’s properties can be seen as the attributes of that object. For example:

let person = {
   name: 'Ada',
   age: 21,
   costume: 'Cheshire Cat'
   trickOrTreat: function(newCandy){
      this.candy.push(newCandy);
   }
}

//tell me about this person!
console.log(person.name + " is a " + person.costume);

This Object represents a thing with name, age and costume attributes (but we haven’t yet indicated that this Object has the classification of “Person”). The value of the trickOrTreat() property is a function (remember: functions are values!), and is an example of how an Object can “have” a function.

  • JavaScript even uses the this keyword to refer to which object that function being called on, just like Java! See below for more on the this keyword and its quirks.

A Class (classification) acts as template/recipe/blueprint for individual objects. It defines what data (attributes) and behaviors (methods) they have. An object is an “instance of” (example of) a class: we instantiate an object from a class. This lets you easily create multiple objects, each of which can track and modify its own data. ES6 classes provide a syntax by which these “templates” can be defined.

Review: Classes in Java

First, consider the following simple class defined in Java (which should be review from earlier programming courses):

//class declaration
public class Person {

    //attributes (private)
    private String firstName;
    private int age;

    //a Constructor method
    //this is called when the class is instantiated (with `new`)
    //and has the job of initializing the attributes
    public Person(String newName, int newAge){
        //assign parameters to the attributes
        this.firstName = newName;
        this.age = newAge;
    }

    //return this Person's name
    public String getName() {
        return this.firstName; //return own attribute
    }

    //grow a year
    public void haveBirthday() {
        this.age++; //increase this person's age (accessing own attribute)
    }

    //a method that takes in a Person type as a parameter
    public void sayHello(Person otherPerson) {
        //call method on parameter object for printing
        System.out.println("Hello, " + otherPerson.getName());

        //access own attribute for printing
        System.out.println("I am " + this.age +  " years old");
    }
}

You can of course utilize this class (instantiate it and call its methods) as follows:

public static void main(String[] args) {
    //instantiate two new People objects
    Person aliceObj = new Person("Alice", 21);
    Person bobObj = new Person("Bob", 42);

    //call method on Alice (changing her fields)
    aliceObj.haveBirthday();

    //call the method ON Alice, and PASS Bob as a param to it
    aliceObj.sayHello(bobObj);
}

A few things to note about this syntax:

  1. You declare (announce) that you’re defining a class by using the class keyword.
  2. Java attributes are declared at the top of the class block (but assigned in the constructor).
  3. Classes have constructor methods that are used to instantiate the attributes.
  4. Class methods are declared inside the class declaration (inside the block, indenting one step).
  5. Class methods can access (use) the object’s attribute variables by referring to them as this.attributeName.
  6. You instantiate objects of the class’s type by using the new keyword and then calling a method with the name of the class (e.g., new Person()). That method is the constructor, so is passed the constructor’s parameters.
  7. You call methods on objects by using dot notation (e.g., object.methodName()).
  8. Instantiated objects are just variables, and so can be passed into other methods.

ES6 Class Syntax

Here is how you would create the exact same class in JavaScript using ES6 syntax:

//class declaration
class Person {

    //a Constructor method
    //this is called when the class is instantiated (with `new`)
    //and has the job of initializing the attributes
    constructor(newName, newAge) {
        //assign parameters to the attributes
        this.firstName = newName;
        this.age = newAge;
    }

    //return this Person's name
    getName() {
        return this.firstName; //return own attribute
    }

    //grow a year
    haveBirthday() {
        this.age++; //increase this person's age (accessing own attribute)
    }

    //a method that takes in a Person type as a parameter
    sayHello(otherPerson) {
        //call method on parameter object for printing
        console.log("Hello, " + otherPerson.getName());

        //access own attribute for printing
        console.log("I am " + this.age +  " years old");
    }
}

And here is how you would use this class:

//instantiate two new People objects
let aliceObj = new Person("Alice", 21);
let bobObj = new Person("Bob", 42);

//call method on Alice (changing her attributes)
aliceObj.haveBirthday();

//call the method ON Alice, and PASS Bob as a param to it
aliceObj.sayHello(bobObj);

As you can see, this syntax is very, very similar to Java! Just like with JavaScript functions, most of the changes have involved getting rid of type declarations. In fact, you can write a class in Java and then just delete a few words to make it an ES6 class.

Things to notice:

  1. Just like in Java, JavaScript classes are declared using the class keyword (this is what was introduced in ES6).

    Always name classes in PascalCase (starting with an Upper case letter)!

  2. JavaScript classes do not declare attributes ahead of time (at the top of the class). Unlike Java, JavaScript variables always “exist”, they’re just undefined until assigned, so you don’t need to explicitly declare them.

    • In JavaScript, nothing is private; you effectively have public access to all attributes and functions.
  3. JavaScript classes always have only one constructor (if any), and the function is simply called constructor().

    • That’s even clearer than Java, where you only know it’s a constructor because it lacks a return type.
  4. Just like in Java, JavaScript class methods are declared inside the class declaration (inside the block, indenting one step).

    But note that you don’t need to use the word function to indicate that a method is a function; just provide the name & parameters. This is because the only things in the class are functions, so declaring it as such would be redundant.

  5. Just like in Java, JavaScript class methods can access (use) the object’s attribute variables by referring to them as this.attributeName.
  6. Just like in Java, you instantiate objects of the class’s type by using the new keyword and then calling a method with the name of the class (e.g., new Person()). That method is the constructor(), so is passed the constructor’s parameters.
  7. Just like in Java, you call methods on objects by using dot notation (e.g., object.methodName()).
  8. Just like in Java, instantiated objects are just variables, and so can be passed into other methods.

So really, it’s just like Java—except that for the differences in how you declare functions and the fact that we use the word constructor to name the constructor methods.

The other difference is that while in Java we usually define each class inside it’s own file, in JavaScript you often create multiple classes in a single file, at the same global “level” as you declared other, non-class functions:

//script.js
'use strict';

//declare a class
class Dog {
    bark() { /*...*/ }
}

//declare another class
class Cat {
    meow() { /*...*/ }
}

//declare a (non-class) function
function petAnimal(animal) { /*...*/ }

//at the "main" level, instantiate the classes and call the functions
let fido = new Dog();
petAnimal(fido); //pass this Dog object to the function

Although the above syntax looks like Java, it’s important to remember that JavaScript class instances are still just normal Objects like any other. For example, you can add new properties and functions to that object, or overwrite the value of any property. Although it looks like a Java class, it doesn’t really behave like one.

Inheritance

The ES6 class syntax also allows you to specify class inheritance, by which one class can extend another. Inheritance allows you to specify that one class is a more specialized version of another: that is, a version of that class with “extra abilities” (such as additional methods).

As in Java, you use the extends keyword to indicate that one class should inherit from another:

//The "parent/super" class
class Dog {
  constructor(name) {
      this.name = name;
  }

  sit() {
      console.log('The dog '+this.name+' sits. Good boy.');
  }

  bark() {
      console.log('"Woof!"');
  }
}

//The "child/sub" class (inherits abilities from Dog)
class Husky extends Dog {
    constructor(name, distance) {
        super(name); //call parent constructor
        this.distance = distance;
    }

    //a new method ("special ability")
    throwFootball() {
        console.log('Husky '+this.name+' throws '+this.dist+' yards');
    }

    //override (replace) parent's method
    bark() {
      super.bark(); //call parent method
      console.log("(Go huskies!)");
    }
}

//usage
let dog = new Husky("Harry", 60); //make a Husky
dog.sit(); //call inherited method
dog.throwFootball(); //call own method
dog.bark(); //call own (overridden) method

In this case, the class Husky is a specialized version of the class Dog: it is a Dog that has a few special abilities (e.g., it can throw a football). We refer to the base, less specialized class (Dog) as the parent or super class, and the derived, more specialized class (Husky) as the child or sub-class.

The sub-class Husky class inherits the methods defined in its parent: even though the Husky class didn’t define a sit() method, it still has that method define because the parent has that method defined! By extending an existing class, you get can get a lot of methods for free!

The Husky class is also able to override its parents methods, defining it’s own specialized version (e.g., bark()). This is useful for adding customization, or for providing specific implementations of callbacks that may be utilized by a framework—a pattern that you’ll see in React.

Note that despite this discussion, JavaScript is not actually an object-oriented language. JavaScript instead uses a prototype system for defining types of Objects, which allows what is called prototypical inheritance. The ES6 class keyword doesn’t change that: instead, it is simply a “shortcut syntax” for specifying Object prototypes in the same way that has been supported since the first version of JavaScript. The class keyword makes it easy to define something that looks and acts like an OOP class, but JavaScript isn’t object-oriented! See this (detailed) explanation for further discussion.

15.2 Arrow Functions

As described in Chapter 11, JavaScript lets you define functions as anonymous values:

let sayHello = function(name) {
  return 'Hello '+name;
}

As you have seen, the use of anonymous functions is incredibly common in JavaScript, particularly when used as anonymous callbacks. Because this is so common, ES6 introduced a simpler, more concise shortcut syntax for quickly specifying anonymous functions. Though officially called lambda functions, they are more commonly known as arrow functions, because of how they utilize an “arrow” symbol =>:

let sayHello = (name) => {
    return 'Hello '+name;
}

To turn an anonymous function into an arrow function, you just remove the function keyword from the definition, and place an arrow => between the parameter list and the block (indicating that the parameter list “goes to” the following block). This saves you a couple of characters when typing.

And for simple callback functions, you can make this even more compact:

  1. If the function takes only a single parameter, you can leave off the () around the parameter list.
  2. If the function body has only a single statement, you can leave the {} off the block.
  3. If the function body has only a single statement AND that statement returns a value, you can leave off the return keyword (the “single statement” arrow function returns the result of the last statement, which will either be an expression or undefined).

Thus the above sayHello method could be written using concise body format as:

let sayHello = name => 'Hello '+name;
  • I recommend you always leave the parentheses () on the parameter list, as it helps with readability (and makes it easier to adjust the parameters later)!

Note that if a function takes no parameters, you must include the () to indicate it is an arrow function:

//normal function syntax
let printHello = function() {
    console.log('Hello world');
}

//arrow syntax
let printHello = () => {
    console.log('Hello world');
}

//concise body
let printHello = () => console.log('Hello world');
  • Despite the above example, it’s better style to include the {} around a function body that doesn’t return a value.

Arrow functions are particularly useful for specifying anonymous callback functions:

let array = [1,2,3]; //an array to work with

//normal function
array.map(function(num) {
  return num*2; //multiply each item by 2
});

//arrow syntax
array.map(num => {
  return num*2; //multiply each item by 2
});

//concise body
array.map(num => num*2);

Arrow functions are great and you should always use them for anonymous callback functions (if your target platform suppo them). They have quickly become the standard way of writing JavaScript, and thus you will see them all over examples and professionally written code.

Working with this

In JavaScript, functions are called on Objects by using dot notation (e.g., myObject.myFunction()). Inside a function, you can refer to the Object that the function was called on by using the this keyword. this is a local variable that is implicitly assigned the Object as a value.

let doggy = {
  name: "Fido",
  bark: function() {
      console.log(this.name, "woofs"); //`this` is object the function was called on
  }
}

doggy.bark(); //=> "Fido woofs"
  • Here the this is assigned the object doggy, what .bark() was called on.

But because functions are values and so can be assigned to multiple variables (given multiple labels), the object that a function is called on may not necessarily be the object that it was first assigned to as a property. this refers to object the function is called on at execution time, not at the time of definition:

//An object representing a Dog
let doggy = {
  name: "Fido",
  bark: function() { console.log(this.name + " woofs"); }
}

// An object representing another Dog
let doggo = {
  name: "Spot",
  bark: function() { console.log(this.name + " yips")}
}

//This is Fido barking
doggy.bark( /*this = doggy*/ ); //=> "Fido woofs"

//This is Spot barking
doggo.bark( /*this = doggo*/ ); //=> "Spot yips"

//This is Fido using Spot's bark!
doggy.bark = doggo.bark; //assign the function value to `doggy`
doggy.bark( /*this = doggy*/) //=> "Fido yips"
  • Notice how the this variable is implicitly assigned a value of whatever object it was called on—even the function is assigned to a new object later!

But because the this variable refers to the object the function is called on, problems can arise for anonymous callback functions that are not called on any object in particular:

class Person {
   constructor(name){ this.name = name; } //basic constructor

   //greet each person in the given array
   greetAll(peopleArray) {
      //loop through each Person using a callback
      peopleArray.forEach(function(person) {
         console.log("Hi"+person.name+", I'm "+this.name);
      });
   }
}

In this example, the greetAll() function will produce an error: TypeError: Cannot read property 'name' of undefined. That is because the this is being called from within an anonymous callback function (the function(person){...})—and that callback isn’t being called on any particular object (notice the lack of dot notation). Since the anonymous callback isn’t being executed on an object, this is assigned a value of undefined (and you can’t access undefined.name).

The solution to this problem is to use arrow functions. An arrow function has the special feature that it shares the same lexical this as its surrounded code: that is, the this will not be reassigned to a (non-existent) object when used within an arrow function:

class Person {
   constructor(name){ this.name = name; }

   greetAll(peopleArray) {
      peopleArray.forEach((person) => { //arrow function (subtle difference)
         console.log("Hi"+person.name+", I'm "+this.name); //works correctly!
      });
   }
}

This property makes arrow functions invaluable when specifying callback functions, particularly once classes and objects are involved. Always use arrow functions for anonymous callbacks!

Alternatively, it is possible to “permanently” associate a particular this value with a function, no matter what object that function is called on. This is called binding the this, and is done by calling the .bind() method on the function and passing in the value you want to be assigned to this. The .bind() method will return a new function that has the value bound to it; often you will then take this new function and “re-assign” it to the old function variable:

//re-assign function
myFunction = myFunction.bind(thisValue);
This is a common pattern in React (and has some minuscule performance benefits), but for this class you should stick with arrow functions for cleanliness and readability.

15.3 Modules

So far, you’ve mostly be writing all of your JavaScript code in a single script (e.g., index.js), even if you’d included some other libraries via additional <script> tags. But as applications get larger and more complex, this single script file can quickly become unwieldy with hundreds or thousands of lines of code implementing dozens of features. Such large files become difficult to read and debug (“where was that function defied?”), can introduce problems with the shared global namespace (“did I already declare a user variable?”), and overall mixes code in a way that violates the Separation of Concerns principle.

The solution to this problem is to split up your application’s code into separate modules (scripts), each of which is responsible for a separate piece of functionality. And rather than loading each module through a separate <script> tag (potentially leading to ordering and dependency issues while continuing to pollute the global namespace), you can define each module as a self-contained script that explicitly “imports” (loads) the functions and variables it needs from other modules. This allows you to better organize your program as it gets large.

While separating code into modules is a common in the Node.js environment, ES6 adds syntax that allows you to treat individual .js files as modules that can communicate with one another. These are known as ES6 Modules.

Browsers do not yet support this syntax! However, modern applications are usually packaged using a bundling build tool such as webpack, which will do the work of “compiling” modules into a single script file (and that we will use with React).

Note that the ECMAScript committee is currently a specification for browser-native module loaders. For example, it is possible to try out modules in the latest versions of Chrome (as of September 2017) and Safari (as of March 2017) by specifying the type=module attribute for a <script> tag (and loading the page via a web server). But the most common solution will be to use an external loading app like webpack.

As in Java, ES6 Modules are able to “load” external modules or libraries by using the import keyword:

//Java example: import `Random` variable from `java.util` to use globally
import java.util.Random
//JavaScript: import the `Random` variable from a `util.js` module
import { Random } from './util';

This is most common version of the ES6 import syntax: you write the keyword import, following by a set of braces { } containing a comma-separated sequence of variables you wish to “import” from a particular module. This is followed by the from keyword, followed by a string containing the relative path to the module script to import. Note that including the extension of the module script is usually optional (by default, ES6 will look for files ending in .js).

  • Be sure to include the ./ to indicate that you’re loading a file from a particular directory. If you leave that off, the module loader will look for a module installed on the load path (e.g., in node_modules/). That is how you load external files such as jQuery though:

    //with jquery installed in `node_modules/`
    //import the `$` and `jQuery` variables
    import {$, jQuery} from 'jquery';

If you want to make a variable available from one module to use in another, you will need to export it (so that it can be “imported” elsewhere). You do this by using the export keyword, placed in front of the variable declaration:

/*** my-module.js ***/
export let question = "Why'd the chicken cross the road?";
export let answer = "To get to the other side";
export function laugh() {
    console.log("hahaha");
}
/*** index.js ***/
import { question, answer, laugh } from './my-module';

console.log(question); //=> "Why'd the chicken cross the road?"
console.log(answer); //=> "To get to the other side"
laugh(); //"hahaha"
  • Note that you can export functions as well as variables, since functions are values!
  • Once a value has been imported, it is available globally (just as if it were defined in a previous <script> tag)

There are a number of ways to export and import variables, giving you significant customization on which values you wish to share and load:

/*** my-module.js ***/
export function foo() { return 'foo'; } //make available (as above)

function bar() { return 'bar'; }
export bar; //export previously defined variable

export { bar as barFunction }; //provide an "alias" (consumer name) for value

//will not be available (a "private" function)
function baz() { return 'baz'; }
/*** index.js ***/
import {foo, barFunction} from './my-module'; //import multiple values
foo() //=> 'foo'
barFunction() //=> 'bar'

import {foo as myFoo} from './my-module'; //provide an "alias" for value
myFoo(); //=> 'foo'

import * as theModule from './my-module'; //import everything that was exported
                                          //loads as a single object with values
                                          //as properties
theModule.foo(); //=> 'foo'
theModule.barFunction(); //=> 'bar'
theModule.baz(); //Error [private function]

Note the additional syntax options in this example:

  • You can use the as keyword to “alias” a variable either when it is exported (so it is shared with a different name) or when it is imported (so it is loaded and assigned a different name). This is particularly useful when trying to produce a clean API for a module (so you export values with consistent names, even if you don’t use those internally), or when you want to import a variable with a very long name.

  • It is possible to just import everything that was exported by a module using the import * as syntax. You specify an object name that will represent the values exported module (e.g., theModule in the example), and each exported variable will be a property of that object. This is particularly useful when you may be adding extra exports to a module during development, but don’t want to keep adjusting the import statement.

  • Note also that only the variables you export are made available to other modules! This allows you to make variables and functions that are “private” to a module!

Finally, each module can also export a single (just one!) default variable, which provides a slight shortcut when importing the variable from that module. You specify the default export by including the default keyword immediately after export:

/*** my-module.js ***/
export default function sayHello() {
    return 'Hello world!';
}
/*** index.js ***/
import greet from './my-module';

greet(); //=> "Hello world!"
  • When importing a default export, you just provide the variable name (“alias”) you wish to refer to that exported value by.
  • Note that it is also possible to make anonymous values into default exports:

    /*** animals.js ***/
    export default ['lion', 'tiger', 'bear']; //export anonymous array

The default export technique is particularly common in object-oriented frameworks like React, where you can make each JavaScript module contain the code for a single class. That single class can be made the default export, allowing other modules to import it quickly and easily as import MyComponent from './MyComponent.js'.

This section describes ES6 modules, which are used with web applications (being part of the ECMAScript specification). However, Node.js utilizes an alternate module loading system called CommonJS. This uses the built-in method require() to load a module (which returns a single “exported” variable as a result). Values are exported from a module by assigning them to the module.exports global. This coure will exclusively utilize ES6 Modules, but it’s good to be aware of the alternate CommonJS approach when searching for help.

15.4 Other Features

Syntactic Sugar causes cancer of the semicolon - Alan Perlis

There are a few other notable syntax options provided by ES6 that you will likely come across.

Template Strings

In ES6, you can declare Strings that contain embedded expressions, allowing you to “inject” an expression directly into a string (rather than needing to concatenate the String with that expression). These are known as template strings (or template literals). Template strings are written in back ticks (``) rather than quotes, with the injected expressions written inside of a ${} token:

let name = 'world';
let greeting = `Hello, ${name}!`; //template string
console.log(greeting); //=> "Hello, world!"
  • Template strings can also include line breaks, allowing you to make multi-line strings!

Note that you can put any expression inside the ${} token; however, it’s best practice to keep the expression as simple as possible (such as by using a local variable) to support readability:

let name = 'world';

//greeting with capitalization. Don't do this!
let greeting = `Hello, ${name.substr(0,1).toUpperCase() + name.substr(1)}!`
console.log(greeting); //=> "Hello, world!";

//do this instead!
let capitalizedName = name.substr(0,1).toUpperCase() + name.substr(1);
let greeting = `Hello, ${capitalizedName}`
console.log(greeting); //=> "Hello, world!";

Destructuring and Spreading

ES6 also introduced destructing assignments, which allow you to assign each element of an array (or each property of an object) into separate variables all in a single operation. You do this by writing the variables you wish to assign to inside of [] on the left-hand side of the assignment—almost like you are assigning to an array!

let array = [1, 2, 3]
let [x, y, z] = array; //assign array elements to `x`, `y`, `z` respectively
console.log(x); //=> 1;
console.log(y); //=> 2;
console.log(z); //=> 3;

If the array contains more than the target number of elements the extra elements will be ignored. However, you can capture them by using the spread operator (...), which is used to either gather or spread a sequence of values (e.g., a list of parameters) into or across an array:

let dimensions = [10, 20, 30, 40];
let [width, height, ...rest] = dimensions
console.log(width);  //=> 10
console.log(height); //=> 20
console.log(rest);   //=> [30, 40];

More commonly, the spread operator is used to specify that a function can take an undefined number of arguments, and to gather all of these objects into a single array:

//a function that logs out all of the parameters
function gather(...args){
    //all the parameters will be grouped into a single array `args`
    args.forEach((arg) => {
        console.log(arg) //can log out all of them, no matter how many!
    });
}

gather('a', 'b', 'c'); //=> "a" "b" "c"
gather(1,2,3,4,5,6); //=> "1" "2" "3" "4" "5" "6"

//a function that adds up all the arguments (no matter how many)
function sum(...numbers) {
    //number is an array, so we can `reduce()` it!
    let total = numbers.reduce((runningTotal, num) => {
        return runningTotal + num; //new total
    }, 0); //start at 0

    return total;

    //or as one line with a concise arrow function:
    return numbers.reduce((total, n) => total+n);
}

sum(3,4,3); // => 10
sum(10,20,30,40); // => 100

These are a few of the more common and potentially useful ES6 features. However, remember that most of these are just “syntactic shortcuts” for behaviors and functionality you can already achieve using ES5-style JavaScript. Thus you don’t need to utilize these features in your code (though they can be helpful), and they will often show up in how we utilize libraries such as React.

Arrow functions are not optional. Always use arrow functions for anonymous callbacks!

Resources