ES6: a definitive Guide to the top

Flávio H. Freitas
12 min readMar 5, 2017

You might be wondering: What is this? Another new thing to learn? JavaScript was not enough and now I need to learn from scratch? Oh, nooo… Calm down, it's just something to make your life easier. It's pretty and you will fall in love, I guarantee.

I first saw this being used when I was attending a Meetup about AngularJS and some guys were talking about it and writing all the code like: () => {} . So pretty and obvious, but for me it was not. I didn't understand the basic idea of it. So, if you are as lost as I was, let's start learning the magic.

ES2015

What is it?

Something you might not know is that JavaScript was created in just 10 days by Brendam Eich in 1995 while he was working on Netscape Navigator.

Since the beginning they had problem when it comes to names. The original name was LiveScript, but it was changed to JavaScript by marketing decision in order to piggyback off the popularity of Java at the time.

Because of its popularity and the competing versions, it was taken to ECMA International so an official language could be formed. Thus we have the official name of the language, ECMAScript (ES), and JavaScript, the trademark of Oracle.

The versions of the language is referenced by the ECMA version number, like ES5 and ES6. And lately the versions were transitioned to a year-based number, so we have ECMAScript 2016, ECMAScript 2017, …

So we have: ES6 = ECMAScript 2015 = ES2015.

So let's have fun

Are you eager too to start using it? I was. So let's start with the magic.

Before opening you preferred IDE and start programming, you need to know that is not all browsers that support ES6. But don't worry, we can solve this by using the compiler Babel.

Comp… what? Yes, basically Babel takes your ES6 smart code and transform it to a code that your old browsers understand. For example:

Your smart code (Code 1):

[1, 2, 3].map(n => n + 1);

It will be compiled to (Code 2):

[1, 2, 3].map(function(n) {
return n + 1;
});

Using Babel

Start creating a folder and add some files to it. You can use the code to make it simpler:

$ mkdir es6-test; cd es6-test$ echo {} > package.json$ mkdir src; echo {} > src/code.js$ echo {} > .babelrc

Set package.json content:

{
"scripts" : {
"build" : "babel src -d output -presets es2015"
}
}

Set .babelrc content:

{
"presets": "es2015"
}

Set code.js content:

[1, 2, 3].map(n => n + 1);

Install Babel CLI and preset:

$ npm install --save-dev babel-cli babel-preset-es2015

Compile the code using:

$ npm run build

And you should have something like this:

Folder with files

If you open the output folder, you will see that the compiler made its work and compiled the file src/code.js (Code 1) to output/code.js (Code 2)

Coding in ES6

Now that you understand how to compile your code and can use it anywhere, let's learn what is new in ES6.

Declaring a variable

Now we have two more ways to declare variables: let and const. Before understand them, it's necessary to know why they were created. The problem that motivated this is named hoisting. For example:

function func() {
console.log(a); //prints undefined
var a = 1;
}

It will print: undefined. This happens because JavaScripts hoists all the function and variables to the top of its declaration. It is interpreted like:

function func() {
var a;
console.log(a);
a = 1;
}

For the same reason you can call the function before it's declared on your code, for example:

func();function func() { ... }

And hoisting can create a lot of problems and strange behavior of your code, for example (Code 3):

var a  = 1;
for(var a = 0; a < 10; a++);
console.log(a); //prints 10

So to solve it we have now let and const. They have block scope instead of function scope from var. For example:

function func(){
var a = 1;
if (true) {
var b = 2;
}
return a + b; // returns 3
}

And with let:

function func(){
let a = 1;
if (true) {
let b = 2;
}
return a + b; // Uncaught ReferenceError: b is not defined
}

And now you don't have the problem from Code 3. Your code gets much more reliable and avoid some strange errors that are not so easy to discover:

let a  = 1;
for(let a = 0; a < 10; a++);
console.log(a); //prints 1

Const is just like let, but its immutable. If you define a value to it, it cannot be changed.

const a = 1;
a = 2; // Uncaught TypeError: Assignment to constant variable.

One thing that we should pay attention with const is that the variable itself is immutable, not the assigned content. For instance, if the content is an object, it can be altered.

const obj = {};
obj.name = 'John';

Arrows

Now function declarations are much more concise. Let's learn with examples:

function func() { … }// becomes:func() { … }

For inline code it gets much much much nicer:

[1, 2, 3].map(function(el) {
return el + 2;
});
// becomes:[1, 2, 3].map(el => el + 2); // you can omit return and ()

And if you have more than one argument:

let a = [1, 2, 3].reduce((acc, current) => acc + current);
console.log(a); // 6

And if you have more than one line on the body:

let a = [1, 2, 3].map(el => {
console.log(el);
return el + 3;
});
console.log(a); // [4, 5, 6]

And if you want to return an object:

let a = [1].map(el => ({val: el, valAcc: el + 3}));
console.log(a); // [{val: 1, valAcc: 4}]

You can also set to a variable

// ES5 (older version)
var sum = function(x, y) {
return x + y;
}
// ES6: you can omit the function word
var sum = (x, y) => {
return x + y;
}
// ES6: and you can also, if it's just a line with return, omit it
var sum = (x, y) => x + y;

Default Parameters

You can add the default value of a variable directly on its definition.

function applyDiscount(cost, discount) {
let discount = discount || 0.1; // 10% of discount;
return cost - (cost * discount);
}
// becomes:function applyDiscount(cost, discount = 0.1) {
return cost - (cost * discount);
}
// or:function defaultDiscountRate {
return 0.1;
}
function applyDiscount(cost, discount = defaultDiscountRate()) {
return cost - (cost * discount);
}

Rest Parameters

One of the problems of JavaScript is how the functions arguments can be accessed, for instance:

var sum = function() {
var result = 0;
for (var i=0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
}

sum(1, 2, 3, 4, 5); // 15

You can access it as an array, but its not exactly one. All the parameters are set to the arguments, but you cannot differentiate them.

With ES6 things got easier and clearer with the rest parameter (…) and you can pass as many parameters as you want.

const sum = (...numbers) => 
numbers.reduce((acc, current) => acc + current, 0)
sum(1, 2, 3, 4, 5) // 15

You can also assign different values:

const sum = (start, ...numbers) =>     
numbers.reduce((acc, current) => acc + current, start)
sum(10, 1, 2, 3, 4, 5) // 25

Spread Parameters

Also you can make the "inverse" of the rest parameter, you can pass a parameter that will be assigned to the parameters of the function.

function sum(x, y) { 
return x + y;
}
let nums = [1, 2];sum(...nums); // 3

Template Strings

Multi line strings declarations that makes your code much more readable and concise:

var element = [
'<div>' +
'<span>hey jude!</span>' +
'</div>'
].join('');
// becomeslet element = `
<div>
<span>hey jude!</span>
</div>
`;

Also to replace values inside the string became much nicer:

var name = 'We will rock you!'
var element = '<span>' + name + '</span>';
// becomes:const name = 'We are the champions';
let element = `<span>${name}</span>`;

Object Enhacements

These are some very nice features you that probably you will use a lot, because your code gets smaller and it's easier to read.

Destructuring

Things got simpler to write and you avoid a lot of code, especially when you are accessing objects.

let [a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
// now with objectslet person = {
name: 'John',
lastName: 'Oliver'
}
let {name, lastName} = person;
console.log(name); // John
console.log(lastName); // Oliver

Also can make it directly in the function:

function greet({name, age}) {
return 'Hey ${name}. ${age} is a good age.'
}
greet({
name: 'Jason',
age: 27
});

You can also skip over items you don’t care about.

let [,,third] = [1, 2, 3];
console.log({third});

Also assign the variables using different names:

let {first: fn, last: ln} = person;
console.log(fn, ln);

Object Shorthands

Now you avoid a lot of redundancy of code when declaring an object or passing an argument. If the name of the variable is the same of the parameter you can omit it:

//ES5 — old way
var name = 'Freddie';
var lastName = 'Mercury';
var person = {
name: name,
lastName: lastName
}
//ES6
var name = 'Fredie';
var lastName = 'Mercury';
var person = {name, lastName};

Function Shorthands

Also to declare a function can be more direct:

//ES5
var person = {
name: 'Henry',
greet: function() {
return 'Hello, ' + this.name;
}
}
//ES6
let person = {
name : 'Henry',
greet() {
return `Hello, ${this.name}`
}
}

Class

If you are used with backend languages like Java, Python, …, this is very similar. You just need to name it and define the functions and attributes of your class.

// ES5
function User(name, email) {
this.name = name;
this.email = email;

this.changeEmail = function(newEmail) {
this.email = newEmail;
}
}

The problem with the declaration above is that the function changeEmail will be redeclared for every single instance of User. So to make it better you could use prototype:

// ES5
function User(name, email) {
this.name = name;
this.email = email;
}
User.prototype.changeEmail = function(newEmail) {
this.email = newEmail;
}

But ES6 you just make it directly:

// ES6
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
changeEmail (email) {
this.email = email;
}
}

Class Inheritance, getters and setters

And talking about Classes, we can define getters and setter:

class Animal {
constructor(name) {
this._name = name;
}

get name() {
return this._name;
}
set name(name) {
this._name = name;
}
}
const animal = new Animal('leopard');
console.log(animal.name); // getter : leopard
animal.name = 'lion'; // setter
console.log(animal.name); // lion

And we can also inherit it and use it with Oriented Objects:

class Animal {
constructor(name){
this.name = name;
}
speak(){
return `${this.name} makes a strange sound`;
}
}
class Dog extends Animal{
speak(){
return `${this.name} barks`;
}
}
const dog = new Dog('Rex');
dog.speak(); // Rex barks

Module

Modules are very important features for programming languages, but Javascript didn't have so far any built-in support for it. But the community has created impressive libraries, like the two important standards, namely CommonJS and Asynchronous Module Definition (AMD) which let developers use modules in JavaScript.

And now ECMAScript 6 brings modules into JavaScript officially. So let's see how it works and start organizing the code better, make reusable components and decrease the complexity of each part of our work.

Writing a module means that you functions and variables won't be visible unless you explicitly export them and import them where you need.

You can export a function like this:

// code.js file
export function addHashtag(value){
return `#${value}`;
}

And import where you need it:

import { addHashtag } from './code.js'addHastag('javascript') // #javascript

You can also define multiple functions on the module:

// code.js file
export function addHashtag(value){
return `#${value}`;
}
export function sum(a, b) {
return a + b;
}

And import:

import { addHashtag, sum } from './code.js'addHastag('javascript'); // #javascript
sum(1, 2); // 3

If you want to export a single value from the module then you can use default export.

// code.js file
export default function addHashtag(value){
return `#${value}`;
}

and import:

import addHashtag from './code.js'addHastag('javascript'); // #javascript

Promises

Before explaining how to use Promises, let's learn why it was developed. The main idea behind Promises is to get away from callback hell. And what is callback hell? So, callback hell (or Pyramid of Doom), is a common problem that appears when you start using a lot of levels of nested indentation to control access to a function while consuming async content. What? Ok, something like this:

async1(function(resultA){
async2(function(resultB){
async3(function(resultC){
async4(function(resultD){
someFunction(resultA, resultB, resultC, resultD);
});
});
});
});

This seems ok when you are dealing with small and easy logics, but when it grows, it becomes a callback hell and you lose easily the control of the code.

To avoid this problem, let's see how Promises work.

// defining itconst p = new Promises(function(resolve, reject) {
// long processing data (db access, website connect, ...)
if (/* condition */) {
resolve(/* value */); // fulfilled successfully
}
else {
reject(/* reason */); // error, rejected
}
})
// consuming it:p.then(res => console.log(res), // defining resolve func
err => console.err(err)) // defining err func

.then() method takes two possible parameters: resolve and reject functions and it is equivalent to:

p.then((val) => console.log("fulfilled:", val))  
.catch((err) => console.log("rejected:", err));
p.then((val) => console.log("fulfilled:", val))
.then(null, (err) => console.log("rejected:", err));

A nice example to understand this using the function .getJSON() from jQuery (fetch JSON results from a url):

let fetchJSON = (url) { 
return new Promise((resolve, reject) => {
$.getJSON(url)
.done((json) =>
resolve(json))
.fail((xhr, status, err) =>
reject(status + err.message));
});
}
// get the JSON file from the url given
fetchJSON('http://...')
.then(res =>
alert(JSON.stringify(res)), // if ok, alert content
err =>
console.err(err)) // if error, show on console

Promises also have some other nice features, like .all() . Promise.all() will not be triggered until all the Promises in the array have been triggered:

// get multiple JSON files
const itemUrls = {'http://...',
'http://...' };
const itemPromises = itemUrls.map(fetchJSON);
Promise.all(itemPromises)
.then(function(results) {
// we just reach here if ALL Promises are done
results.forEach((item) => {
// process item
});
})
.catch(function(err) {
// we reach here if any Promise fails
console.log("Failed:", err);
});

And finally the freaking callback hell from the beginning can be elegantly solved as:

function promiseA() { return new Promise((resolve, reject) => setTimeout(resolve, 100)); }
function promiseB() { return new Promise((resolve, reject) => setTimeout(resolve, 200)); }
function promiseC() { return new Promise((resolve, reject) => setTimeout(resolve, 300)); }
function promiseD() { return new Promise((resolve, reject) => setTimeout(resolve, 300)); }
Promise.all([promiseA(), promiseB(), promiseC(), promiseD]).then(([a, b, c, d]) => {
someFunction(a, b, c, d);
});

Generators

One of the things generators differs from normal function is with respect to the "run to completion" expectation. It means, if you start processing a function, it will run to completion before any other function run. For example:

setTimeout(function(){
console.log("Hello World");
},1);
function foo() {
// NOTE: don't ever do crazy long-running loops like this
for (var i=0; i<=1E10; i++) {
console.log(i);
}
}
foo();
// 0..1E10
// "Hello World"

And generators allow a function to stop at a particular point, and later it can resume from there and continue the processing. Stop again, resume, stop, … I think you understood.

To create a generator you start with a special symbol (*) after the function word. (don't get confused, this * has nothing to do with the pointers in C Language)

function *numbers() {…}
// or
function* numbers(){…}

And to return values you use the keyword yield. And you will can have a function like:

function *numbers() {
console.log('The magic has started');
yield 1;
yield 2;
yield 3;
}
const iterator = numbers();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: undefine, done: true}

There are some ways to iterate over the generators:

function *numbers() {
yield 1;
yield 2;
yield 3;
}
const iterator = numbers();for( let i in iterator) {
console.log(i); // it will print:
// 1
// 2
// 3
}
// or also iterate using the spread operator:
console.log([...numbers()]); // [1, 2, 3]

Sets

They are collections that each item must be unique. Let's see with code:

const items = new Set(['itemA', 'itemB', 'itemC', 'itemA', 'itemB', 'itemC']);console.log(items); // Set{‘itemA’, ‘itemB’, ‘itemC’}
// it ignores the repeated items
// you can add
items.add('itemD');
// you can delete
items.delete('itemA');
//you an check if a value exists
items.has('itemA');
//you can iterator over it
items.forEach(item => console.log(item));
// get its size
items.size
// clear the whole set
items.clear();

And now you know a little bit more about ES6. I hope this article was useful for you.

If you like what you read be sure to like it (← a ❤️ on the left) — as a writer it means the world 😘

If you interested in more info you can find on the following links:

Thank you for reading 😚

If you enjoyed this article, be sure to like it give me a lot of claps — it means the world to the writer 😘 And follow me if you want to read more articles about Culture, Technology, and Startups.

Flávio H. de Freitas is an Entrepreneur, Engineer, Tech lover, Dreamer and Traveler. Has worked as CTO in Brazil, Silicon Valley and Europe.

--

--