
JavaScript ECMAScript 6 features
A good understanding of ECMAScript 6 features is critical for all JavaScript developers going forward. The language features introduced in ECMAScript 6 represent the foundation upon which JavaScript applications will be built for the foreseeable future.
Block Bindings
In most languages, variables are created at the spot where they are declared. In Javascript, however, where the variables are created depends on how they are declared.
var Declarations and Hoisting
When a variable is declared using the var keyword the declarations are moved to the top of the enclosing scope regardless of where the declaration occurs. This is called Hoisting. For example:
function getValue(condition) {
if (condition) {
var value = "blue";
// other code
return value;
} else {
// value exists here with a value of undefined
return null;
}
// value exists here with a value of undefined
}
In the above function, the variable value gets created no matter if the condition is true or not. The above function can be rewritten as below:
function getValue(condition) {
var value;
if (condition) {
value = "blue";
// other code
return value;
} else {
return null;
}
}
Block-level declarations
The variables declared inside a block are accessible only within that block. Block scopes are also called lexical scopes. Block-level declarations are created:
- Inside a function.
- Inside a block of code.
let Declarations
When a variable is declared using let, the variable is available only in the enclosing block unlike var which is function-scoped. Also, the variables are created where they are declared and not hoisted to the top of their scope.
No Redeclaration
Declaring a variable with let with the same name as the one declared with var in the same scope throws an error. Declaring variables in the current scope and parent scope doesn’t throw any error.
var count = 30;
// throws an error
let count = 40;
var count = 30;
if (condition) {
// doesn't throw an error
let count = 40;
// more code
}
const Declaration
A variable may be declared with the const keyword marking it as a constant. These variables once declared cannot be changed hence they should always be declared and initialized together. A const declaration on an object only prevents an object being modified but not its members.
const person = {
name: "Nicholas"
};
// works
person.name = "Greg";
// throws an error
person = {
name: "Greg"
};
Functions
Functions are an important part of any programming language, and prior to ECMAScript 6, JavaScript functions hadn’t changed much since the language was created. This left a backlog of problems and nuanced behaviour that made making mistakes easy and often required more code just to produce very basic behaviours.
Functions with Default Parameter Values
In Javascript often you might have to support function calls with different number of parameters by initializing by default the missing parameters. Below example shows how it is done before ES6:
function makeRequest(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function() {};
// the rest of the function
}
The timeout would by default be assigned 2000 if the parameter is undefined. This would mostly work except for when timeout is 0 which is a falsy value and hence timeout would be assigned a value of 2000.
The ES6 solves this by allowing default parameter values in the function arguments as below:
function makeRequest(url, timeout = 2000, callback = function() {}) {
// the rest of the function
}
In the above code timeout will be 2000 and callback will be assigned empty function when there are no values.
The default parameter values need not be a primitive values they can be expressions that return primitive values as below:
function getValue() {
return 5;
}
function add(first, second = getValue()) {
return first + second;
}
console.log(add(1, 1)); // 2
console.log(add(1)); // 6
Working with Unnamed Parameters: Rest Parameters
The default parameters allows to work with a function when there are fewer parameters. The Rest parameters allows to work with the function when there are more parameters than specified. The Rest parameter is a parameter with three dots […] preceding the name. The parameter then becomes an array with the rest of the parameters passed to the function.
function pick(object, …keys) {
let result = Object.create(null);
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = object[keys[i]];
}
return result;
}
The Rest parameter have two restrictions:
- There can only be one rest parameter and the rest parameter is always the last parameter.
- Rest parameter cannot be used as an object literal setter as there can only be a single parameter to the setter.
The Spread operator
Where the rest operator combines the arguments to a function into an array, The spread operator takes in an array, splits it up and passes each of the array elements as the parameters to the function
let values = [25, 50, 75, 100]
// equivalent to
// console.log(Math.max(25, 50, 75, 100));
console.log(Math.max(…values)); // 100
In the above code since Math.max takes any number of parameters the values array is passed using the spread operator.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is the arrow function. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>). But arrow functions behave differently than traditional JavaScript functions in a number of important ways:
No this, super, arguments, and new.target bindings
The values of this, super, arguments, and new.target inside the function are defined by the closest containing non-arrow function.
Cannot be called with new
Arrow functions do not have a [[Construct]] method and therefore cannot be used as constructors. Arrow functions throw an error when used with new.
No prototype
Because you can’t use new on an arrow function, there’s no need for a prototype. The prototype property of an arrow function doesn’t exist.
Can’t change this
The value of this inside the function can’t be changed. It remains the same throughout the entire life cycle of the function.
No arguments object
Because arrow functions have no arguments binding, you must rely on named and rest parameters to access function arguments.
No duplicate named parameters
Arrow functions cannot have duplicate named parameters in strict or non-strict mode, as opposed to non-arrow functions, which cannot have duplicate named parameters only in strict mode.
Below are different flavours of arrow functions along with their ES5 equivalents:
let reflect = value => value;
// effectively equivalent to:
let reflect = function(value) {
return value;
};
let sum = (num1, num2) => num1 + num2;
// effectively equivalent to:
let sum = function(num1, num2) {
return num1 + num2;
};
let getName = () => "Nicholas";
// effectively equivalent to:
let getName = function() {
return "Nicholas";
};
let sum = (num1, num2) => {
return num1 + num2;
};
// effectively equivalent to:
let sum = function(num1, num2) {
return num1 + num2;
};
let doNothing = () => {};
// effectively equivalent to:
let doNothing = function() {};
Destructuring
In ES5 inorder to extract values from objects and arrays into local variables, it took alot of duplicate code as shown in the below example:
let options = {
repeat: true,
save: false
};
// extract data from the object
let repeat = options.repeat,
save = options.save;
In ES6 however with destructuring we can extract the values from objects into local variables in a single expression as below:
let node = {
type: "Identifier",
name: "foo"
};
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
In this code, the value of node.type is stored in a variable called type, and the value of node.name is stored in a variable called name.
When assigning to different local variable names:
let node = {
type: "Identifier",
name: "foo"
};
let { type: localType, name: localName } = node;
console.log(localType); // "Identifier"
console.log(localName); // "foo"
Array destructuring syntax is very similar to object destructuring: it just uses array literal syntax instead of object literal syntax. The destructuring operates on positions within an array rather than the named properties that are available in objects. For example:
let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
Here, array destructuring pulls out the values “red” and “green” from the colors array, and stores them in the firstColor and secondColor variables. Those values are chosen because of their position in the array; the actual variable names could be anything. Any items not explicitly mentioned in the destructuring pattern are ignored. Keep in mind that the array isn’t changed in any way.
Sets
ECMAScript 6 adds a Set type that is an ordered list of values without duplicates. Sets allow fast access to the data they contain, adding a more efficient manner of tracking discrete values.
Sets are created using new Set(), and items are added to a set by calling the add() method. You can see how many items are in a set by checking the size property:
let set = new Set();
set.add(5);
set.add("5");
console.log(set.size); // 2
Sets don’t coerce values to determine whether they’re the same. That means a set can contain the number 5 and the string “5” as two separate items. You can also add multiple objects to the set, and those objects will remain distinct:
let set = new Set(),
key1 = {},
key2 = {};
set.add(key1);
set.add(key2);
console.log(set.size); // 2
Because key1 and key2 are not converted to strings, they count as two unique items in the set. If they were converted to strings, they would both be equal to “[object Object]” instead. If the add() method is called more than once with the same value, all calls after the first one are effectively ignored:
let set = new Set();
set.add(5);
set.add("5");
set.add(5); // duplicate - this is ignored
console.log(set.size); // 2
Maps
The ECMAScript 6 Map type is an ordered list of key-value pairs, where the key and the value can be any type. Key equivalence is determined by calling the Object.is() method, so you can have a key of 5 and a key of “5” because they’re different types. This is quite different from using object properties as keys, because object properties always coerce values into strings. You can add items to maps by calling the set() method and passing it a key and the value to associate with the key. You can later retrieve a value by passing the key to the get() method. For example:
let map = new Map();
map.set("title", "Understanding ECMAScript 6");
map.set("year", 2016);
console.log(map.get("title")); // "Understanding ECMAScript 6"
console.log(map.get("year")); // 2016
In this example, two key-value pairs are stored. The “title” key stores a string, and the “year” key stores a number. The get() method is called later to retrieve the values for both keys. If either key didn’t exist in the map, get() would have returned the special value undefined instead of a value. You can also use objects as keys, which isn’t possible when you’re using object properties to create a map in the old workaround approach. Here’s an example:
let map = new Map(),
key1 = {},
key2 = {};
map.set(key1, 5);
map.set(key2, 42);
console.log(map.get(key1)); // 5
console.log(map.get(key2)); // 42
This code uses the objects key1 and key2 as keys in the map to store two different values. Because these keys are not coerced into another form, each object is considered unique. This allows you to associate additional data with an object without modifying the object.
Iterators
Iterators are objects with a specific interface designed for iteration. All iterator objects have a next() method that returns a result object. The result object has two properties: value, which is the next value, and done, which is a Boolean that’s true when there are no more values to return. The iterator keeps an internal pointer to a location within a collection of values, and with each call to the next() method, it returns the next appropriate value. If you call next() after the last value has been returned, the method returns done as true and value contains the return value for the iterator. That return value is not part of the data set; rather, it’s a final piece of related data or undefined if no such data exists. An iterator’s return value is similar to a function’s return value in that it’s a final way to pass information to the caller.
With this information in mind, creating an iterator using ECMAScript 5 is possible, as shown here:
function createIterator(items) {
var i = 0;
return {
next: function() {
var done = (i >= items.length);
var value = !done ? items[i++] : undefined;
return {
done: done,
value: value
};
}
};
}
var iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// for all further calls
console.log(iterator.next()); // "{ value: undefined, done: true }"
Generators
A generator is a function that returns an iterator. Generator functions are indicated by an asterisk character (*) after the function keyword and use the new yield keyword. It doesn’t matter if the asterisk is directly next to function or if some whitespace is between it and the * character, as in this example:
// generator
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// generators are called like regular functions but return an iterator
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
The * before createIterator() makes this function a generator. The yield keyword, also new to ECMAScript 6, specifies values the resulting iterator should return when next() is called and the order in which they should be returned. The iterator generated in this example has three different values to return on successive calls to the next() method: first 1, then 2, and finally 3. A generator gets called like any other function, as shown when iterator is created.
Built in iterators
ECMAScript 6 has three types of collection objects: arrays, maps, and sets. All three have the following built-in iterators to help you navigate their content:
- entries() Returns an iterator whose values are key-value pairs
- values() Returns an iterator whose values are the values of the collection
- keys() Returns an iterator whose values are the keys contained in the collection
You can retrieve an iterator for a collection by calling one of these methods.
Javascript classes
The simplest class form in ECMAScript 6 is the class declaration, which looks similar to classes in other languages.
A Basic Class Declaration
Class declarations begin with the class keyword followed by the name of the class. The rest of the syntax looks similar to concise methods in object literals but doesn’t require commas between the elements of the class. Here’s a simple class declaration:
class PersonClass {
// equivalent of the PersonType constructor
constructor(name) {
this.name = name;
}
// equivalent of PersonType.prototype.sayName
sayName() {
console.log(this.name);
}
}
let person = new PersonClass("Nicholas");
person.sayName(); // outputs "Nicholas"
console.log(person instanceof PersonClass); // true
console.log(person instanceof Object); // true
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass.prototype.sayName); // "function"
The class declaration for PersonClass behaves similarly to PersonType in the previous example. But instead of defining a function as the constructor, class declarations allow you to define the constructor directly inside the class using the special constructor method name. Because class methods use concise syntax, there’s no need to use the function keyword. All other method names have no special meaning, so you can add as many methods as you want.
Own properties, properties that occur on the instance rather than the prototype, can only be created inside a class constructor or method. In this example, name is an own property. I recommend creating all possible own properties inside the constructor function so a single place in the class is responsible for all of them.
Interestingly, class declarations are just syntactic sugar on top of the existing custom type declarations. The PersonClass declaration actually creates a function that has the behavior of the constructor method, which is why typeof PersonClass gives “function” as the result. The sayName() method also ends up as a method on PersonClass.prototype in this example, similar to the relationship between sayName() and PersonType.prototype in the previous example. These similarities allow you to mix custom types and classes without worrying too much about which you’re using.
Why Use the Class Syntax?
Despite the similarities between classes and custom types, you need to keep some important differences in mind:
- Class declarations, unlike function declarations, are not hoisted. Class declarations act like let declarations, so they exist in the temporal dead zone until execution reaches the declaration.
- All code inside class declarations runs in strict mode automatically. There’s no way to opt out of strict mode inside classes.
- All methods are nonenumerable. This is a significant change from custom types, where you need to use Object.defineProperty() to make a method nonenumerable.
- All methods lack an internal [[Construct]] method and will throw an error if you try to call them with new.
- Calling the class constructor without new throws an error.
- Attempting to overwrite the class name within a class method throws an error.
Modules
A module is JavaScript code that automatically runs in strict mode with no way to opt out. Contrary to a shared-everything architecture, variables created in the top level of a module aren’t automatically added to the shared global scope. The variables exist only within the top-level scope of the module, and the module must export any elements, like variables or functions, that should be available to code outside the module. Modules may also import bindings from other modules.
Basic Exporting
You can use the export keyword to expose parts of published code to other modules. In the simplest case, you can place export in front of any variable, function, or class declaration to export it from the module, like this:
// export data
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// export function
export function sum(num1, num2) {
return num1 + num1;
}
// export class
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
// this function is private to the module
function subtract(num1, num2) {
return num1 - num2;
}
// define a function…
function multiply(num1, num2) {
return num1 * num2;
}
// …and then export it later
export multiply;
There are a few details to notice in this example. Apart from the export keyword, every declaration is the same as it would be in a script. Each exported function or class also has a name, because exported function and class declarations require a name. You can’t export anonymous functions or classes using this syntax unless you use the default keyword. Also, consider the multiply() function, which isn’t exported when it’s defined. That works because you don’t always need to export a declaration: you can also export references. Additionally, notice that this example doesn’t export the subtract() function. That function won’t be accessible from outside this module because any variables, functions, or classes that are not explicitly exported remain private to the module.
Basic Importing
When you have a module with exports, you can access the functionality in another module by using the import keyword. The two parts of an import statement are the identifiers you’re importing and the module from which those identifiers should be imported. This is the statement’s basic form:
import { identifier1, identifier2} from " ./example.js";
The curly braces after import indicate the bindings to import from a given module. The keyword from indicates the module from which to import the given binding. The module is specified by a string representing the path to the module (called the module specifier). Browsers use the same path format you might pass to the <script>
element, which means you must include a file extension. Node.js, on the other hand, follows its convention of differentiating between local files and packages based on a filesystem prefix. For instance, an example would be a package and ./example.js would be a local file.
When you’re importing a binding from a module, the binding acts as though it was defined using const. As a result, you can’t define another variable with the same name (including importing another binding of the same name), use the identifier before the import statement, or change the binding value.
References
Zakas, Nicholas C.. Understanding ECMAScript 6: The Definitive Guide for JavaScript Developers. No Starch Press. Kindle Edition.

