ES6 / ES7 / 2015 / 2016 / NEXT 

ECMAScript (ES) is a standard for scripting languages. JavaScript is an implementation of ECMAScript. Netscape submitted its first version of LiveScript (later renamed JavaScript) to ECMA and since then every implementation of JS has really been ECMAScript.

ES5 is supported by all major current browsers and backwards compatible to IE9, Safari 6, FF 21, and Chrome 23

ES6 is the newest version and was the most extensive update since 1997. The overhaul was so major the name was later changed to ES2015 with the intention to put out new updates every year.



Now there seems to be uncertainty weather we call it ES6 / ES7 / ES2015 / ES2016 or just ES.NEXT. All of these names are interchangable and fully understood by the industry.



ES2015 compatibility: https://kangax.github.io/compat-table/es6/



Although ES2015 is not fully supported compilers like Babel exist to take ES2015 and convert it at runtime to ES5.

So if ES2015 isn’t fully supported and we just transform it to ES5 at runtime why even bother?

Faster, Cleaner, less error prone code!!!


Let & Const

var x = true;
if (x)
var xIsTrue = true;
else
var xIsALie = true;
console.log(xIsTrue);
console.log(xIsALie);
>> true
>> undefined
As you can see in the above example we have access to both variables even though xIsALie is never initialized.  This is called hoisting and can lead to unexpected failures.  When your code is compiled JavaScript actually hoists all variables to the top.
var x = true;
var xIsTrue;
var xIsALie;
if (x)
xIsTrue = true;
else
xIsALie = true;

Having access to variables that we shouldn't instead of throwing an error can lead to unexpected consequences within our app.

With let variables are scoped to the innermost { }

Which means if x is not true we will not ever have access to xIsALie and we will get an error that it is undefined.

This is much better to know why we don't have access rather than to just see it as undefined.

var is always hoisted to the top of it's function which means using var in a callback can become even more confusing.  If we had a for loop like this:

var colors = ["Red", "Blue", "Green"];
function addColor(url, color, cb) {
setTimeout(function(){ return cb(); }, 3000);
}
for( var i in colors) {
addColor("/colors", colors[i], function() {
console.log(colors[i]);
});
}
>> "Green"
>> "Green"
>> "Green"

To fix this bug we can fix the for loop by changing var to let.

for( let i in colors) {
addColors("/colors", colors[i], function() {
console.log(colors[i]);
});
}
>> "Red"
>> "Blue"
>> "Green"

To recap var is always hoisted and therefore scoped to the nearest function.

Let is scoped to the nearest set of { }.

NOTE:  let can't be reassigned

let x = true;
let x = false;
>> TypeError: Identifier 'x' has already been declared
The proper way to reassign let is without the declaration:
let x = true;
x = false;

Const is another variable declaration that will be used to replace let.  

const is short for constant meaning these values CAN'T change.

const is great because it removes the "magic number" pattern that we often fall into as developers.

A magic number is a literal value without a clear meaning:

if (colors.length > 4) {
// Do something
}
if (friends.length > 4) {
// Do something
}

In the above code what does 4 mean?  Does it mean the same thing in both places?  If it does and we need to change it then we need to change it in both places.

Instead we should be clear about magic number declarations and we can do that by using const.

NOTE:  const is READ ONLY

const MAX_COLORS = 3
const MAX_FRIENDS = 4
if (colors.length > MAX_COLORS) {
//Do something
}
if (friends.length > MAX_FRIENDS) {
// Do something
}

Now our numbers have actual meaning and if they are used in more places we only have to change it in the const declaration.

NOTE: const can't be reassigned

const MAX_USERS = 3;
MAX_USERS = 4;
>> TypeError: Assignment to constant variable.

Functions

The first improvement to functions is default parameters.

Let's say you had a function that expects one argument.  JavaScript will allow us to call the function without parameters. So what happens if we try to call a method on the parameter?  It breaks!

function countColors(colors) {
return colors.length;
}
countColors();
>> TypeError: Cannot read property 'length' of undefined
The above code breaks but in ES2015 we can set default params:
function countColors(colors = []) {
console.log(colors.length);
}
countColors();
countColors(['Red', 'Blue', 'Green']);
>> 0
>> 3

In the code above the first time countColors is called it will be an empty array since nothing was passed in.

In the second call we passed in an array so it overrides the default.

You can have can set anything as a default param but defaults should go last and if you have multiple defaults you will need to call the function with as many arguments until you reach the one you want defaulted.  

Here is one way to set an object as a default parameter.

function showUserInfo(name, options = {}) {
let likes = options.likes;
let followers = options.followers;
let following = options.following;
...
}

In the above code we set variables to expected options.

We can make this code better with a new feature called named parameters.

function showUserInfo(name, { likes, followers, following } = {} ) {
console.log(likes);
console.log(followers);
console.log(following);
}
Now this method can be called with as many of the named parameters as we want:
showUserInfo('Dave', { likes: 4, following: 13 });

Rest Params

Rest parameters allow you to accept an infinite number of arguments.  Rest params are indicated by using 3 dots and a name.  They will be read in as an array:
function showTrendingTags(...hashTags) {
for(let i in hashTags) {
displayTopic(hashTags[i]);
}
}
Another example:
function logColors(...colors) {
for(let i in colors) {
console.log(colors[i]);
}
}
console.log('NO COLORS')
logColors();
console.log('TWO COLORS')
logColors('red', 'white');
console.log('THREE COLORS')
logColors('red', 'green', 'blue');
>> NO COLORS
>> TWO COLORS
>> red
>> white
>> THREE COLORS
>> red
>> green
>> blue
Rest params are always the last parameters passed in:
function logColors(set, ...colors) {
for(let i in colors) {
console.log(set + ": " + colors[i]);
}
}
logColors('blank');
logColors('two colors', 'red', 'white');
logColors('three colors', 'red', 'green', 'blue');

Spread operators are similar to rest parameters except they are used to call a function instead of in the function signature.  Lets say we get a response back from the server as an array.  We can use spread operators to send the array as individual arguments instead of an array.

getMovies("/movies", function(data) {
let movies = data.movies;
displayMovies(...movies);
});

The syntax for rest params is identical to that of spread operators.

Rest params are used in function signatures to take individual arguments and turn them into an array.

Spread operators are passed to functions and used to take an array and pass it as individual parameters.

Here is a code snippet to further demonstrate the difference:

function colors(...colors) {
console.log(colors);
}
let colors1 = ["Red", "Blue", "Green"];
let colors2 = ["Black", "Purple", "White"];
console.log("Spread Ops");
console.log( ...colors1);
console.log(...colors2);
console.log("Rest Params")
colors(...colors1);
colors(...colors2);
>> Spread Ops
>> Red Blue Green
>> Black Purple White
>> Rest Params
>> ["Red", "Blue", "Green"]
>> ["Black", "Purple", "White"]

Arrow Functions

Arrow functions were brought in to ES2015 to help with scope.  Remember when we were using AJAX and needed access to "this" we had to set "this" to another variable outside of the callback.  This is because "this" inside of a callback refers to the callback and not the original function.  Arrow functions can help with this.

The old way:

var self = this;
$.ajax({
url: '/movies',
type: 'GET'
}).done( function(data) {
self.setState(data);
});
Notice we had to set this to a different variable outside of the callback.  Here is how we can fix this code with arrow functions.
$.ajax({
url: '/movies’,
type: 'GET'
}).done( (data) => {
this.setState(data);
});
First we remove the keyword function and then add the => at the end.  Now the scope of the original "this" is preserved.
We can also use => to define functions
function doubleNumber(num) {
return num * 2;
}
Can now be written like this:
const doubleNumber = (num) => {
return num * 2;
}

Objects Initializer

Objects have some major improvements in ES2015 as well.

Look at the following code snippet:

function officerInfo(first, last, rank, email) {
let fullName = first + " " + last;
return({first: first, last: last, fullName: fullName, rank: rank, email: email})
}
let officer = officerInfo("Clive", "Savacool", "Chief", "clive@fakeemail.com");
console.log(officer.first);
console.log(officer.last);
console.log(officer.email);
console.log(officer.rank);
console.log(officer.fullName);

This code is fine except for the return statement.  Our object keys are the same as our variable names so there is a lot of repetition.

With ES2015 when the keys match the variable names we can shorten this up.

function officerInfo(first, last, rank, email) {
let fullName = first + " " + last;
return { first, last, fullName, rank, email };
}
let officer = officerInfo("Clive", "Savacool", "Chief", "clive@fakeemail.com");

We can still call properties of officer the same way.

You can create objects on the fly like this not just inside of methods

let title = "The Martian";
let stars = 3.5;
let comments = ["Cool movie", "A great comedy"];
let movie = { title, stars, comments
console.log(movie.title)
>> The Martian

Object Destructuring

Take the following code snippet:
let officer = officerInfo("Clive", "Savacool", "Chief", "clive@fakeemail.com");
let first = officer.first;
let last = officer.last;
let fullName = officer.fullName;
let email = officer.email;
let rank = officer.rank;
This seems very repetitive and unnecessary.  This can be cleaned up with desturcturing:
let { first, last, fullName, email, rank } = officerInfo("Clive", "Savacool", "Chief", "clive@fakemeail.com");
We can also select subsets of properties this way:
let { fullName } = officerInfo("Clive", "Savacool", "Chief", "clive@fakemeail.com");

Function Initializer

Let's look at a function that has a function inside of it:
function movieInfo(title, description, stars) {
let longTitle = title + ": " + description;
const MIN_STARS = 2.5;
return {
title,
description,
longTitle,
worthWatching: function() {
return stars >= MIN_STARS;
}
}
}

Notice the function in the return.  There is a new syntax for initializing function:

The function keyword is no longer necessary so we can simply pull it out and remove the :

function movieInfo(title, description, stars) {
let longTitle = title + ": " + description;
const MIN_STARS = 2.5;
return {
title,
description,
longTitle,
worthWatching() {
return stars >= MIN_STARS;
}
}
}

String Interpolation

Often we find ourselves building strings with variables and + signs and maybe a string another + then another variable:
let longTitle = title + ': ' + description;
With string interpolation we can remove the + signs all together. 
let longTItle = `${title}: ${description}`;

To interpolate a string we wrap the string in ` and anytime we want to use a variable we wrap it in ${variable}.

This cleans up a lot of code.

String interpolation is also great for multi line strings.  Before:

"Hello,\nthis is a message\nonmultiple lines"
After:
`Hello
this is a message
on multiple lines`

Object.Assign

Our goal is to write modular code that can be moved from project to project.  Sometimes the functionality will change slightly depending on where and how it is used.  We can have an options hash to take care of this.

Let's write a quick function that will display a flash message and make it disappear within a specified amount of time.

function flashAlert(target, msg, options = {}) {
....
}
Depending on the app and the place it is used functionality might change based on options passed in.  In order to do this without having our app error out if an option is missing it is common to use a default object.
function flashAlert(target, msg, options = {}) {
let defaults = {
duration: 10,
klazz: 'success',
exitLocation: 'top',
container: '.nav'
}
}

We want to make this function so that anything that passes in only overwrites the defaults that are specified.  This is where Object.assign comes in.

function flashAlert(target, msg, options = {}) {
let defaults = {
duration: 10,
klazz: 'success',
exitLocation: 'top',
container: '.nav'
}
let settings = Object.assign({}, defaults, options);
}

This takes 3 params, first an empty hash, second the hash to be merged into and third the hash to merge.  

This will merge the two hashes overwriting any that are specified in options and store them in an object called settings.

To try this in action load this code into CodePen:

function numbers(options = {}) {
let defaults = {
min: 1,
max: 100,
timeout: 40
}
let settings = Object.assign({}, defaults, options);
console.log(settings);
}
numbers();
numbers({min: 5});
numbers({min:2, top: 101});
One of the advantages of Object.assign is that we can still access the original default values incase we need to compare them.

Object.assigns short hand

There is a shorter way to write Object.assigns now that you understand how it works by using object destructuring.

function numbers(options = {}) {
let defaults = {
min: 1,
max: 100,
timeout: 40
}
let settings = {...defaults, ...options }
console.log(settings);
}
numbers();
numbers({min: 5});
numbers({min:2, top: 101});

Arrays

Array Destructuring:

We often access arrays by their index like this:

let colors = ["Red", "Blue", "Green"];
let red = colors[0];
let blue = colors[1];
let green = colors[2];

This can be cleaned up by using Array Destructuring:

let colors = ["Red", "Blue", "Green"];
let [ red, blue, green ] = colors;

We can also discard values this way:

let colors = ["Red", "Blue", "Green"];
let [ red, , green ] = colors;
Array Destructuring with rest params:
let colors = ["Red", "Blue", "Green", "White", "Black"];
let [ first, ...rest] = colors
console.log(first, rest);
> "Red" ["Blue", "Green", "White", "Black"];

for of loop

Before our best option was to use a forEach loop with a callback or the for in loop:
let friends = ["Jake", "Brad", "Brandon", "Ethan", "Ryan"];
for( index in friends) {
console.log(friends[index]);
}
Here is the same loop using for of:
let friends = ["Jake", "Brad", "Brandon", "Ethan", "Ryan"];
for( name of friends) {
console.log(name);
}

This is much cleaner code and a lot easier to understand.

NOTE: for of will not work on objects by default.

Array.find

Let's look at the the following code snippet:
let users = [
{ name: 'Dave', role: 'Instructor' },
{ name: 'Brad', role: 'TA' },
{ name: 'Parker', role: 'TA' },
{ name: 'Sara', role: 'Student'},
{ name: 'Jill', role: 'Student'},
{ name: 'Greg', role: 'Student'}
]

How would you find the first Student in this array?  Previously we would have to loop through the array of objects and return if role === 'Student'.

With Array.find it is much simpler:

let users = [
{ name: 'Dave', role: 'Instructor' },
{ name: 'Brad', role: 'TA' },
{ name: 'Parker', role: 'TA' },
{ name: 'Sara', role: 'Student'},
{ name: 'Jill', role: 'Student'},
{ name: 'Greg', role: 'Student'}
]
let student = users.find( (user) => {
return user.role == "Student";
});

We can actually shorten up this method even more by removing the parens and the return keyword.

let parker = users.find( user => user.name == "Parker" );
console.log(parker.role);
There is also an Array.findIndex which is very handy for finding the index of an element in an array.

Maps

Up to this point we have been using objects to store key value pairs and it has been fine but it does come with some unexpected behavior.  Look at the following code what do you expect to log to the screen?
let user1 = { name: "Dave"};
let user2 = { name: "Jake"};
let age = {};
age[user1] = 35;
age[user2] = 26;
console.log(age[user1]);
console.log(age[user2]);
If you expected:
> 35
> 26
You would be wrong although right to expect that.  What you actually get is:
​> 26
> 26

This is because when we use objects as maps it's keys are always converted to strings and thus overwrites previous entries.  Behind the scenes the object user1 and the object user2 are converted to the string "[Object, object]" and that is why it overrides the first record.

Now to convert the above code to use a map:

let user1 = { name: "Dave"};
let user2 = { name: "Jake"};
let age = new Map();
age.set(user1, 35);
age.set(user2, 26);
console.log(age.get(user1));
console.log(age.get(user2));

You wan't to use Map when keys for the map are unknown until runtime.

Maps can also be used with the for of loop.

let movies = new Map();
movies.set("title", "Lord of the rings");
movies.set("description", "People returning jewlery");
movies.set("stars", 3.5);
for( let [key, value] of movies) {
console.log(`${key}: ${value}`);
}

Maps have a few helpful methods

map.get(key); => value

map.set(key, value); => sets key value pair

map.has(key); => true / false

map.delete(key); => removes key value pair from map

WeakMaps

A weak map is a map that only allows objects as keys.  Weak maps are more memory efficient because they don't prevent the garbage collector from cleaning up objects that are no longer referenced anywhere but inside the map.

Sets

Arrays do not enforce uniqueness.  The following array is perfectly valid:
let colors = ["Red", "Blue", "Green", "Red"];
Sets do enforce uniqueness:
let colors = new Set();
colors.add("Red");
colors.add("Blue");
colors.add("Red");
console.log(colors.size);
> 2

Notice the second red is ignored.

Sets work with both the for of loop and destructuring just like arrays.

WeakSets

Just like weak maps, weak sets only allow objects as values and are more memory efficient.  Weak sets provide two additional methods: has and delete.

Classes

Prior to classes we could achieve class like objects by using a constructor function like this:
function Movie(title, description, stars) {
this.title = title;
this.description = description;
this.stars = stars;
}
Movie.prototype.info = function() {
let info = ` Title ${this.title}
Description ${this.description}
Stars ${this.stars}`
console.log(info);
}
let movie = new Movie("Titanic", "Riding on boats", 3);
movie.info();

Above we create a constructor function and then prototype a method.

We create an object with the new keyword and then we can call prototyped functions on the object.

Using the class syntax we can write better object oriented JavaScript with less code and less repetition.  This approach is both cleaner, easier to maintain and less error prone.

Using a class the above code would change to this:

class Movie {
constructor(title, description, stars) {
this.title = title;
this.description = description;
this.stars = stars;
}
info() {
let movieInfo = ` Title ${this.title}
Description ${this.description}
Stars ${this.stars}`
console.log(movieInfo);
}
}
let movie = new Movie("Titanic", "Riding on boats", 3);
movie.info();

The constructor is called every time a new instance of the class is instantiated.  This is a good place to define instance variables and any other code that should run when an instance of the class is created.

We then have class instance methods such as info.

Inheritance

Inheritance is a way to have a child class inherit properties and functions from a parent class as well as be able to overwrite functions if needed.

class Animal {
constructor(name) {
this.name = name;
}
eat(){
console.log(`${this.name} is eating...`);
}
}
class Snake extends Animal {
constructor(name){
super(name);
}
move() {
console.log(`${this.name} is slithering`);
}
}
class Monkey extends Animal {
constructor(name) {
super(name);
}
move() {
console.log(`${this.name} is swinging`);
}
}
let animal = new Animal('Pip');
let snake = new Snake('Hiss');
let monkey = new Monkey('Frank');
animal.eat();
snake.eat();
monkey.eat();
snake.move();
monkey.move();

The parent class is Animal the sub classes are Monkey and Snake which extend Animal.  Calling super() will call the parent classes constructor.

Notice all animals have eat even though it was only defined in the parent.  Each child class has their own move function.  Each subclass has to call super as the first line of their constructor.

Const as a Module

Remember constants are defined to their closest block.  If you have constants that need to be used throughout the app you would need to redefine them everywhere.  One way around this is to use a module to define constants:

constants.js

const PER_PAGE = 10;
const MAX_USERS = 25;
const DEFAULT_COLOR = 'red';
export { PER_PAGE, MAX_USERS, DEFAULT_COLOR }
something.js
import { PER_PAGE, MAX_USERS, DEFAULT_COLOR } from './constants.js'
Now all of these constants are available throughout the file and can be reused in other files with a similar import statement.
pagination.js
import { PER_PAGE } from './constants.js'

If we decide to change the PER_PAGE constant to 20 we only have to do it in constants.js and it will be reflected everywhere the module is imported.

Classes can also be encapsulated in modules:

export default class Notification {
constructor(msg) {
this.msg = msg;
}
alertMessage() {
alert(this.msg);
}
logMessage() {
console.log(msg);
}
}
And to import:
import Notification from ./notification.js
let notification = new Notification("Success");
notification.alertMessage();
notification.logMessage();

Promises

Promises are a major improvement over callback based code.  We use callback because JavaScript is single threaded which means we need to write non blocking code in order to keep the page from freezing while things are happening.

Blocking code might look something like this:

let movies = getMovies();
renderMovies(movies);

In this example we have to wait for the first function to finish before the second function is called.  We fix this with callbacks:

getMovies( function(movies) {
renderMovies(movies);
});
This code is fine unless we have to start nesting callbacks:
getMovies( function(movies) {
sortMovies(movies, function(error, sortedMovies) {
if(error) {
renderError( function(error) {
});
} else {
renderMovies(movies);
}
});
});

This code quickly becomes unreadable and hard to maintain.

With Promises the code becomes easier to read and maintain.

getMovies()
.then(sortMovies())
.then(renderMovies())
.catch( function(error) {
console.log(error)
});
To implement a promise:
function getMovies() {
return Promise(resolve, reject) {
//...
resolve(movies);
//...
reject(movies);
});
}
resolve is invoked on success and reject is invoked when an error occurs.

The promise lifecycle

When a promise is created it is set to the state of pending.

Next it is either fulfilled (resolve) or rejected.

Promises return a future value so the function no longer needs a callback as an argument.

let movies = getMovies();

When the promise is fulfilled the value of movies will be set.

Here is a more detailed implementation of a promise:

function getMovies() {
return new Promise( function(resolve, reject) {
let url = '/movies',
let request = new XMLHttpRequest;
request.open('GET', url, true);
request.onload = function() {
if (request.status == 200) {
resolve(JSON.parse(request.response));
}
};
request.send();
}
Now that we have a resolved promise:
let movies = getMovies();
movies.then( function(results) {
sortMovies(results);
});

An even cleaner way would be to chain results:

getMovies()
.then( function(results) {
sortMovies(results);
}).then( function(sortedMovies) {
renderMovies(sortedMovies);
});
We can pass fulfilled promises down the chain as well.
If reject is called in any of the promises execution is automatically moved to the catch skipping any other then functions.

Generators

Generators are special functions in which we can use the keyword yield.  They are defined with the * character.
function *movieList() {
yield "Titanic";
yield "Rounder";
yield "Star Wars"
}
With this generator we can run the function through the for of loop:
for( let movie of movieList) {
console.log(movie);
}
We can also use it with the spread oporator:
let movies = [...movieList()];
console.log(movies);
More importantly we can use generators to make plain JavaScript that we can iterate over:
let movie = { title: 'Ghostbusters', stars: 5, year: 1984 }
movie[Symbol.iterator] = function *() {
let properties = Object.keys(this);
for(let p of properties) {
yield this[p]
}
}
for( let value of movie ) {
console.log(movie);
}
> 'Ghostbusters'
> 5
> 1984

Imports

Before:
var React = require('react')
After:
import React from 'react'
Why does this matter?
Before:
var React = require('react')
var component = React.Component
class Foo extends Component {
After:
import React, { Component } from 'react'
class Foo extends Component {
WORSE!
var router = require('react-router');
var indexRoute = router.indexRoute;
var link = router.Link;
var browserHistory = router.browserHistory;
BETTER:
import { IndexRoute, Link, browserHistory } from 'react-router'

Named imports

//functions.js
export default const someFunction = () => {
}
export const namedFunction = () => {
}
import someFunction from ‘functions’
import { namedFunction } from ‘functions'

Using ES6 today:

Currently the most popular way to start using ES6 in your projects is babel:
https://babeljs.io/
When using babel via NPM or CDN it can read from a config file called .babelrc
Your .babelrc might look something like this:
{
"presets": ["es2015", "react", "stage-0"]
}
This is telling Babel that we are using ES2015, react (for JSX transpiling) and stage-0 features.  NOTE:  Stage-0 is not a very stable way to build production apps. 
Next we need some type of build tool like gulp, grunt or my personal choice Webpack.
NOTE:  You can use the CDN and include your JavaScript files using:
<script type="text/babel">main.js</script>
This is by far the easiest way to get started right away.

Webpack

Im not going to go too much into webpack here.  You can learn more by going through the simple tutorial here:
http://webpack.github.io/docs/tutorials/getting-started/
A typical Webpack config file using Webpack with Babel for ES6 might look something like this:
// Example webpack configuration with asset fingerprinting in production.
'use strict';
var path = require('path');
var webpack = require('webpack');
var devServerPort = 3808;
var config = {
entry: {
'application': ./app/application.js'
},
output: {
path: path.join(__dirname, '..', 'public'),
publicPath: /public/',
filename: production ? '[name]-[chunkhash].js' : '[name].js'
},
resolve: {
root: path.join(__dirname, '..', ‘app'),
extensions: ["", ".js", ".jsx", ".es6"]
},
module: {
loaders: [
{
test: /\.jsx?$/, // Match both .js and .jsx files
exclude: /node_modules/,
loader: "babel",
query:
{
presets:['es2015', 'react', 'stage-0']
}
}
]
},
};
module.exports = config;