After years of work, the ES6 specification was released on June 2015. It presented the biggest advancements in JavaScript since ES5 and introduced several features into the language that will completely transform the way we JavaScript developers write code. It would be ambitious to describe all the improvements made by ES2015. Instead, let's try to work through the basic features we'll use in the next chapters.
Modules are now a supported language-level feature. They allow developers to wrap their component in a Module pattern, and export and import modules inside their code. The implementation is very similar to the CommonJS module implementation described in the previous chapters, although ES2015 modules also support asynchronous loading. The basic keywords for working with ES2015 modules are export
and import
. Let's look at a simple example. Suppose you have a file named lib.js
that contains the following code:
export function halfOf(x) { return x / 2; }
So, in your main.js
file, you can use the following code:
import halfOf from 'lib'; console.log(halfOf(84));
However, modules can be much more fun. For instance, let's say our lib.js
file looks like this:
export function halfOf(x) { return x / 2; } export function multiply(x, y) { return x * y; }
In your main file, use the following code:
import {halfOf, multiply} from 'lib'; console.log(halfOf(84)); console.log(multiply(21, 2));
ES2015 modules also support default export
values. So, for instance, let's say you have file named doSomething.js
that contains the following code:
export default function () { console.log('I did something') };
You'll be able to use it as follows in your main.js
file:
import doSomething from 'doSomething'; doSomething();
It is important to remember that the default import should identify their entities using the module name.
Another important thing to remember is that modules export bindings and not values. So for instance, let's say you have a validator.js
file that looks like this:
export let flag = false; export function touch() { flag = true; }
You also have a main.js
file that looks like this:
import { flag, touch } from 'validator'; console.log(flag); touch(); console.log(flag);
The first output would be false
, and the second would be true
. Now that we have a basic understanding of modules, let's move to classes.
The long debate about classes versus prototypes came to a conclusion that classes in ES2015 are basically just a syntactic sugar over the prototype-based inheritance. Classes are easy-to-use patterns that support instance and static members, constructors, and super calls. Here is an example:
class Vehicle { constructor(wheels) { this.wheels = wheels; } toString() { return '(' + this.wheels + ')'; } } class Car extends Vehicle { constructor(color) { super(4); this.color = color; } toString() { return super.toString() + ' colored: ' + this.color; } } let car = new Car('blue'); car.toString(); console.log(car instanceof Car); console.log(car instanceof Vehicle);
In this example, the Car
class extends the Vehicle
class. Thus, the output is as follows:
(4) in blue true true
Arrows are functions shorthand by the =>
syntax. For people familiar with other languages such as C# and Java 8, they might look familiar. However, arrows are also very helpful because they share the same lexical this
as their scope. They are mainly used in two forms. One is using an expression body:
const squares = numbers.map(n => n * n);
Another form is using a statement body:
numbers.forEach(n => { if (n % 2 === 0) evens.push(n); });
An example of using the shared lexical would be:
const author = { fullName: "Bob Alice", books: [], printBooks() { this.books.forEach(book => console.log(book + ' by ' + this.fullName)); } };
If used as a regular function, this
would be the book
object and not the author
.
Let
and Const
are new keywords used for symbol declaration. Let is almost identical to the var
keyword, so it'll behave the same as global and function variables. However, let
behaves differently inside a block. For instance, look at the following code:
function iterateVar() { for(var i = 0; i < 10; i++) { console.log(i); } console.log(i) } function iterateLet() { for(let i = 0; i < 10; i++) { console.log(i); } console.log(i) }
The first function will print i
after the loop, but the second one will throw an error, since i
is defined by let
.
The const
keyword forces single assignment. So, this code will throw an error as well:
const me = 1 me = 2
Default, Rest, and Spread are three new features related to functions parameters. The default feature allows you to set a default value to the function parameter:
function add(x, y = 0) { return x + y; } add(1) add(1,2)
In this example, the value of y
will be set to 0
if a value is not passed or is set to undefined
.
The Rest feature allows you to pass an array as trailing arguments as follows:
function userFriends(user, ...friends) { console.log(user + ' has ' + friends.length + ' friends'); } userFriends('User', 'Bob', 'Alice');
The Spread feature turns an array into a call argument:
function userTopFriends(firstFriend, secondFriend, thirdFriends) { console.log(firstFriend); console.log(secondFriend); console.log(thirdFriends); } userTopFriends(...['Alice', 'Bob', 'Michelle']);