ES2015 Tutorial
Table of Contents
Babel Setup
We will use Babel to transform the ES2015 (ES6) JavaScript to plain old ES5 code.
Install the Babel CLI and ES2015 Presets
Run the following command within your project directory:
$ yarn add babel-cli add babel-preset-es2015 -D
Create .babelrc
configuration file to determine which presets to use.
{
"presets": ["es2015"]
}
Update the package.json
file to configure npm build
command, so it will run babel transformation.
{
"scripts": {
"build": "babel src -d dist"
},
"devDependencies": {
//
}
}
The -d
option is to specify the output directory. So when we run npm run build
, the ES2015 JavaScript files within the src
directory will be transformed into ES5 JavaScript files on dist
directory.
var is Hoisted to Function Block
If we use var
to declare a variable within the conditional or loop block, it will be hoisted (moved) to its function block.
function hello() {
// var greeting is hoisted up here:
// var greeting;
if (false) {
var greeting = 'Hello my friend!';
} else {
console.log(greeting); // undefined
console.log(unknown); // ReferenceError
}
}
The greeting
variable will be hoisted to the function block, as if it’s declared on the very top of the function block. On the else
block, the greeting
value will be undefined
while the unknown
variable will throw a ReferenceError
since it’s not declared anywhere.
So it’s a good practice to always declare variable at the very top of the block, to avoid confusion.
var VS let & const
While var
declaration is hoisted to the function level block, the new let
and const
keywords will always be declared within its block. So there will be no surprise.
function hello() {
if (false) {
var greeting = 'Hello my friend!';
let question = 'How are you today?';
} else {
console.log(greeting); // undefined
console.log(question); // ReferenceError
}
}
While the greeting
variable will be hoisted to the block function, the question
declaration will stay within the if
block. That’s why the greeting
variable on else
block will be undefined
while accessing the question
will throw ReferenceError
.
Similar to the let
, the const
keyword will also not be hoisted.
const Is Not Really Immutable
The const
keyword will not allow the identifier to be reassigned.
const programmingLanguages = ["Javascript", "Python", "Ruby"];
// TypeError: Assignment to constant variable.
programmingLanguages = ["Scala", "Haskell"];
However, the value it contains is not really immutable. Although the programmingLanguages
identifier cannot be reassigned, it’s array value can be manipulated like so:
const programmingLanguages = ["Javascript", "Python", "Ruby"];
programmingLanguages.push("Scala");
// The value changes!
console.log(programmingLanguages);
Template String
In the past we need to concatenate string with +
sign if we want to use a variable or multiline string. Now with template string, we no longer need any of those. The template string is always enclosed by the back tick. Here’s an example of using a variable named name
within a template string.
let name = "John Doe";
console.log(`Hello my name is ${name}.`); // Hello my name is John Doe.
With template string, you can also use multiple line out of the box:
let alert = `
<div class="alert">
<p>Something went wrong!</p>
</div>
`
The Arrow Function
Suppose we have these lines of code to print out each element within the array:
[10, 20, 30].forEach(function(item) {
console.log(item);
});
We can turn it into an arrow function like so:
[10, 20, 30].forEach((item) => {
console.log(item);
});
Arrow Function with One Parameter
If the arrow function only use one parameter, we can omit the parentheses:
[10, 20, 30].forEach(item => {
console.log(item);
});
One Liner Arrow Function
If the arrow function is only consist of one line statement, we can even turn it into a simpler form without the curly braces:
[10, 20, 30].forEach(item => console.log(item));
Arrow Function Without Parameter
If the arrow function does not use any parameter, we must use an empty parentheses:
[10, 20, 30].forEach(() => console.log('Hello!'));
Arrow Function with Multiple Parameters
Here’s an example of an arrow function with two or more parameters:
[10, 20, 30].forEach((item, index) => console.log(`${index} => ${item}`));
Implicit Return
If the arrow function is written into one-line statement, it will implicitly return the statement’s value:
// No need return statement.
let timesTwo = [10, 20, 30].map((item) => item * 2);
// Still need return statement.
let timesThree = [10, 20, 30].map((item) => {
return item * 3;
});
The this keyword
On strict mode, the this
keyword within the traditional callback function is always bound to the callback function itself, even if it’s declared within the class.
class FooBar {
baz() {
[1, 2, 3].forEach(function(item) {
console.log(this); // undefined.
});
}
}
While with the arrow function, the this
keyword is keep bounded to the class instance:
class FooBar {
baz() {
[1, 2, 3].forEach(item => {
console.log(this); // FooBar {}
});
}
}
Default Parameter
On ES2015, we now have a simple way of setting up a default parameter value for a function. Below is the applyDiscount
function with default value of discountPercentage
parameter is set to 10
.
function applyDiscount(cost, discountPercentage = 10) {
return cost - (cost * (discountPercentage / 100));
}
applyDiscount(100); // 90
applyDiscount(100, 25); // 75
We can even set a default value for parameter by calling another function:
function getDefaultPercentage() {
return 10;
}
function applyDiscount(cost, discountPercentage = getDefaultPercentage()) {
return cost - (cost * (discountPercentage / 100));
}
The Rest Parameters
The rest parameters allow us to accept an indefinite number of arguments and turn it into an array. For example, we have a function sum
that will sum any given number as the argument:
function sum(...numbers) {
// numbers = [argument-1, argument-2, ... argument-n]
return numbers.reduce((prev, current) => prev + current);
}
sum(10, 100, 1000); // 1110
sum(1, 2, 3, 4, 5); // 15
If you need another argument for a function, make sure that the rests parameter is on the last order.
function sum(message, ...numbers) {
let total = numbers.reduce((prev, current) => prev + current);
console.log(`${message}: ${total}`);
}
sum('The total sum is', 10, 100, 1000); // The total sum is: 1110
The Spread Syntax
The spread syntax is a companion for the rest parameters. It allows us to expand an array into a multiple arguments (for function call) or multiple elements (for array construction) or multiple variables.
Here’s an example of spread syntax for turning an array into multiple arguments:
function greet(firstName, lastName, age) {
console.log(`My name is ${firstName} ${lastName}, I am ${age} years old!`);
}
let john = ["John", "Doe", 25];
greet(...john); // My name is John Doe, I am 25 years old!
Object Shorthand
In the past, if we are about to return an object we do it something like this:
function getPerson() {
var name = 'John Doe';
var age = 25;
return {
name: name,
age: age
};
}
Now with ES2015, if the variable name is similar with the object’s key, we can entirely omit the keys:
function getPerson() {
let name = 'John Doe';
let age = 25;
return { name, age };
}
getPerson(); // Object {name: "John Doe", age: 25}
Method Shorthand
In the past, if we need to create a method for an object, we need to use function
keyword:
function getPerson() {
var name = 'John Doe';
return {
name: name,
greet: function() {
console.log('Hello my name is ' + this.name);
}
};
}
Now with ES2015, we just need to declare it with greet()
:
function getPerson() {
let name = 'John Doe';
return {
name,
greet() {
console.log(`Hello my name is ${this.name}`);
}
};
}
getPerson().greet(); // Hello my name is John Doe
Object Destructuring
In the past if we want to assign an object’s key to a variable, we do it like this:
var person = {
name: 'John Doe',
age: 25
};
var name = person.name;
var age = person.age;
Now with object destructuring in ES2015, we can simplify that process like so:
let person = {
name: 'John Doe',
age: 25
};
let { name, age } = person;
console.log(name); // John Doe
console.log(age); // 25
We can also use object destructuring syntax in function argument:
function displayData({ message, data }) {
console.log(`Message: ${message}`); // Message: Success!
console.log(`Data: ${data}`); // Data: 999999
}
displayData({
httpStatusCode: 200,
message: 'Success!',
data: 999999
});
Classes
In the past we can achieve a class-like feature in JavaScript like this:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log("My name is " + this.name + "!");
};
var john = new Person("John", 25);
john.greet(); // My name is John!
Now in ES2015, we have a syntactical sugar for creating a class. Note that it just a syntactical sugar over the old prototype based inheritance, not introducing the object-oriented inheritance model.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`My name is ${this.name}!`);
}
}
let john = new Person("John", 25);
john.greet(); // My name is John!
The Static Method
The ES2015 also introduce a syntactical sugar for static method. Like any other language, the static method is belonged to the class and is not callable by the instance.
class Person {
constructor(name) {
this.name = name;
}
static isMammals() {
return true;
}
}
Person.isMammals(); // true
Getter Method
The getter method can be used to get a dynamically computed value. For example the Person
class instance can have the bornIn
properly which is dynamically calculated based on the given age
parameter.
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
get bornIn() {
return (new Date).getFullYear() - this.age;
}
}
let john = new Person("John", 25);
console.log(`John was born in ${john.bornIn}`); // John was born in 1997
Setter Method
Setter method can be used to execute a function whenever a class’s property is attempted to be changed. Here’s an example of setter method that will uppercase the name
property.
class Person {
set name(val) {
this._name = val.toUpperCase();
}
get name() {
return this._name;
}
}
let john = new Person();
john.name = 'john';
console.log(`Name: ${john.name}`); // Name: JOHN
Promise
Promise is kind of a placeholder for an asynchronous code.
function timer(duration) {
return new Promise((resolve, reject) => {
console.log('Timer is started...');
setTimeout(() => {
console.log('Timer is stopped...');
resolve();
}, duration * 1000)
});
}
A Promise
required one parameter named executor, which is a function passed with two arguments: resolve
and reject
. The executor usually contains an asynchronous codes. Within it, we call resolve()
to resolve the promise.
We can use our timer
function like so:
let promise = timer(3);
promise.then(() => console.log('Success!')); // The is the resolve() function
Reject a Promise
We can reject a promise by calling reject()
within the executor:
function timer(duration) {
return new Promise((resolve, reject) => {
if (duration <= 0) {
return reject('Duration must be larger than 0!');
}
console.log('Timer is started...');
setTimeout(() => {
console.log('Timer is stopped...');
resolve();
}, duration * 1000)
});
}
Now, if we pass duration <= 0
, the resolve()
function will never be called and the reject()
function will be called instead.
let promise = timer(-3);
promise.then(() => console.log('Success!'))
.catch(error => console.error(error)); // This is the reject() function
We can also reject the promise by simply throws an error:
if (duration <= 0) {
throw 'Duration must be larger than 0!';
}
String Methods
ES2015 includes several new methods for String:
The includes
method is used to check whether the string contains the given string argument:
"John Doe".includes("Doe"); // true
The startsWith
method is used to check whether the string starts with the given string argument.
"John Doe".startsWith("John"); // true
"John Doe".startsWith("Doe", 5); // true
The endsWith
method is used to check whether the string ends with the given string argument.
"John Doe".endsWith("Doe"); // true
"John Doe".endsWith("Jo", 2); // true
The repeat
method is used to repeat the string x-times.
"ha".repeat(3); // hahaha
Array Methods
There are also several new methods for array:
Though it’s not officially included on ES2015 standard, the includes
method is already adopted by major browsers. It used to find whether the array contains the given element:
[2, 3, 4].includes(4); // true
[2, 3, 4].includes(100); // false
The find
method will find the first element that match the given conditional argument.
[2, 3, 4, 6, 8].find((item) => item > 5); // 6
[2, 3, 4, 6, 8].find((item) => item > 100); // undefined
The findIndex
is similar with the find
method, but it will return the array’s index rather than its value:
[2, 3, 4, 6, 8].findIndex((item) => item > 5); // 3
[2, 3, 4, 6, 8].find((item) => item > 100); // -1
Generator
The generator is a function that can be paused and exit the function and later at some point can be resumed again. The generator will return an iterator and is marked by the asterisk symbol (*
).
And in order to use generator we have to install the babel-polyfill
:
$ yarn add babel-polyfill -D
And if you use Webpack, include the babel-polyfill
to the entries
option like so:
module.exports = {
entry: [
'babel-polyfill',
'./src/main.js'
],
}
To create a generator, use *
right before the function name. You can use the yield
keyword to return a value and set a pause point.
function *numbers() {
console.log('Generator begin...');
yield 1;
yield 2;
yield 100;
}
Note that if you simply call the numbers()
it will not do anything. The generator will return an iterator which you can use in many ways, one of them is with the next()
method:
let iterator = numbers();
// Print "Generator begin..." on the first next() call
console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: 2, done: false}
console.log(iterator.next()); // Object {value: 100, done: false}
console.log(iterator.next()); // Object {value: null, done: true}
Accessing Iterator with for…of
Suppose we have a generator that could generate a range from start
until end
:
function *range(start, end) {
while (start <= end) {
yield start;
start++;
}
}
We can loop through the iterator with the following syntax:
let iterator = range(7, 10);
for (let i of iterator) {
console.log(i); // 7, 8, 9, 10
}
Note that with for
loop, the i
value is no longer an object, but already the value of the current step.
Converting Iterator to Array with Spread Syntax
We can also use spread syntax to convert an iterator into an array:
let myRange = [...range(7, 10)];
console.log(myRange); // [7, 8, 9, 10]
Set
Set is a collection of unique items. To create it we can simply pass an array into Set
constructor like so:
let languages = new Set(['PHP', 'Laravel', 'Vue']);
Set Properties & Methods
Set has various properties and methods that we can use to manipulate the collection:
languages.size; // The size of the collection: 3
languages.add('Swift'); // Add new item to collection
languages.delete('Swift'); // Remove an item from collection
languages.values(); // Return an iterator of the collection
languages.clear(); // Clear all items from the collection
Convert Set to an Array
We can easily convert a Set into an array by using the spread operator:
let languagesArr = [...languages]; // ['PHP', 'Laravel', 'Vue']
Loop Through Set
We can loop through set with for...of
syntax like this:
for (language of languages) {
console.log(language);
}