ES2015 Intro

Reference

I’ve been looking forward to this for a while! Although I’ve used ES6 in several projects and courses here and there, until now I haven’t really had a proper introduction to the basic ins & outs. This is the overview of all of the course sections on ES6 (aka ES2015).

For reference:

Const

The const variable sets a value that cannot be changed for strings, numbers, booleans, null, undefined, or Symbol. Note that arrays and objects can be changed:

var bim = "bim"; // bim
bim = "bam"; // bam

const bim = "bim"; // bim
bim = "bam"; // TypeError: Assignment to constant variable.
const bim = "bam"; // SyntaxError: Identifier 'bim' has already been declared

const bam = [1, 2, 3];
bam.push(4); // [1, 2, 3, 4]

Let

The let variable sets a value with scope only in certain blocks (if, for, while, do, try, catch, finally). Let can be reassigned, but cannot be redefined after its initial declaration. One difference between var and let: although let does hoist to the top of the block it’s in, it’s definition does not and trying to access it will result in a Reference Error:

function hello() {
return hello;
var hello = "ME!"; }
hello() // undefined

function goodbye() {
return goodbye;
let goodbye = "YOU!"; }
goodbye() // ReferenceError goodbye is not defined

function cheers() {
let cheers = "CHEERS!"; }
return cheers;
cheers() // "CHEERS!"

Let is useful when you want a variable to only be accessible in a specific scope. An example with setTimeout:

// As below, var is global and the for loop runs before setTimeout
for(var i = 0; i < 5; i++){
setTimeout(function(){
console.log(i);
},1000)
}
// 5 (five times)

// Pre-ES2015, the fix is to put a new function inside setTimeout:
for(var i = 0; i < 5; i++){
(function(j){
setTimeout(function(){
console.log(j);
},1000);
})(i)
}
// 0
// 1
// 2
// 3
// 4

// With let, i is not global so a 2nd function isn't necessary:
for(let i = 0; i < 5; i++){
setTimeout(function(){
console.log(i);
},1000);
}
// 0
// 1
// 2
// 3
// 4

Template Strings

Template strings give a more streamlined way to concatenate strings:

// Old way:
console.log("Welcome " + name + ", enjoy your time here!");

// New way:
console.log(`Welcome ${name}, enjoy your time here!`);

You can also write multi-line strings with backticks.

Arrow Functions

Functions can also be streamlined with arrow functions. And bonus: if the function is on one line, no curly braces or return statement are needed:

// ES5
var add = function(a,b){
return a+b;
}

// ES2015
var add = (a,b) => {
return a+b;
}

// ES2015 shorter
var add = (a,b) => a+b;

Another examples with more involved functions:

// ES5
function doubleAndFilter(arr){
return arr.map(function(value){
return value * 2;
}).filter(function(value){
return value % 3 === 0;
})
};
doubleAndFilter([5,10,15,20]); // [30]

// ES2015
var doubleAndFilter = arr => arr.map(val => val * 2).filter(num => num % 3 === 0);

doubleAndFilter([5,10,15,20]); // [30]

Note that is there is only one argument in the function, it does not need to be wrapped in parentheses.

One really important thing about arrow functions is that unlike using the function keyword, the keyword this is not automatically attached to the function. Instead, this is attached to the enclosing context. So be sure to use the function keyword if this is necessary:

var instructor = {
firstName: "Elie",
sayHi: function(){
setTimeout(() => {
console.log("Hello " + this.firstName);
}, 1000);
}
}

instructor.sayHi(); // "Hello Elie"

For this reason, it’s bad form to use arrow functions as methods on an object!

Another important difference is how the arguments keyword works. It doesn’t! Well, it doesn’t work inside a function, but it does work if it’s called inside a function that’s in another function.

Default Parameters

Super cool! You can set a default value for the parameters of a function with ES6. If you pass one argument to a function with two parameters, it will consider the argument as the first default). Examples:

function add(a=7, b=9) {
return a + b;
}

add() // 16
add(1) // 10
add(1, 4) // 5

For…of Loops

Works in places where for...in loops can’t, like in arrays. Can’t be used on objects, or any other data type without the Symbol method in its prototype. Strings, arrays, maps, and sets do have this symbol iterator.

Rest & Spread Operators

Uses ... as a placeholder for the rest of the arguments in a function:

// ES5
function sumArguments(){
var total = 0;
for(var i = 0; i < arguments.length; i++){
total += arguments[i];
}
return total;
}

// A little fancier ES5
function sumArguments(){
var argumentsArray = [].slice.call(arguments);
return argumentsArray.reduce(function(accumulator,nextValue){
return accumulator + nextValue;
});
}

// ES2015
var sumArguments = (...args) => args.reduce((acc, next) => acc + next);

When ... is not used as arguments, it’s a spread operator and can be used to spread out all of the values in an array:

var arr1 = [1,2,3];
var arr2 = [4,5,6];
var arr3 = [7,8,9];

// ES5
var combined = arr1.concat(arr2).concat(arr3);

// ES2015
var combined = [...arr1, ...arr2, ...arr3];

This can be useful with methods that can’t take an array as a parameter:

var arr = [3,2,4,1,5];

Math.max(arr); // NaN

// ES5
Math.max.apply(this, arr); // 5

// ES2015
Math.max(...arr); // 5
function sumValues(a,b,c){
return a+b+c;
}

var nums = [12,15,20];

// ES5
sumValues.apply(this, nums); // 47

// ES2015
sumValues(...nums); // 47

Improvements For Working With Objects

There are several syntax changes which make it easier to work with objects using ES6:

Defining Objects:

var firstName = "Nia";
var lastName = "Murrell";

// ES5 - repetitive!
var instructor = {
firstName: firstName,
lastName: lastName
}

// ES2015 - streamlined!
var instructor = {
firstName,
lastName
}

Adding Methods to Objects:

// ES5
var instructor = {
sayHello: function(){
return "Hello!";
}
}

// ES2015 - do NOT use arrow functions here!
var instructor = {
sayHello(){
return "Hello!";
}
}

Adding Properties to Objects:

// ES5
var firstName = "Elie";
var instructor = {};
instructor[firstName] = "That's me!";

instructor.Elie; // "That's me!"

// ES2015
var firstName = "Elie";
var instructor = {
[firstName]: "That's me!"
}

instructor.Elie; // "That's me!"

Object.Assign

In ES5 you can’t assign an object to another without changing the original object:

// ES5

var o = {name: "Elie"};
var o2 = o;

o2.name = "Tim";
o.name; // "Tim"

To get around this reference issue, we can use the assign method:

// ES2015

var o = {name: "Elie"};
var o2 = Object.assign({},o);

o2.name = "Tim";
o.name; // "Elie"

Object.assign() accepts a series of objects and returns a new object with all of the keys and values assigned to it from the series. To create a new object, be sure to include an empty object as the first parameter. Otherwise it will alter the first parameter with all of the new key-value pairs added to it.

But do note: it doesn’t create a deep clone; if this is necessary it will need to be hard coded:

// ES2015

var o = {instructors: ["Elie", "Tim"]};
var o2 = Object.assign({},o);

o2.instructors.push("Colt");

o.instructors; // ["Elie", "Tim", "Colt"];

Improvements For Working With Arrays

Array.from()

Array.from() allows us to create an actual array from an array-like object (as in from strings, maps, sets, ...arguments, DOM selectors, etc.). These objects look like an array but don’t have all of the array methods attached:

// ES5
var divs = document.getElementsByTagName("div"); // returns an array-like-object
divs.reduce // undefined, since it is not an actual array

var converted = [].slice.call(divs) // convert the array-like-object into an array
converted.reduce // function reduce() { ... }

// ES2015
var divs = document.getElementsByTagName("div");
var converted = Array.from(divs);

var firstSet = new Set([1,2,3,4,3,2,1]); // {1,2,3,4}
var arrayFromSet = Array.from(firstSet); // [1,2,3,4]

Find and FindIndex

Find is a useful way to search through an array for a value without using a for loop. The find() method accepts a callback with value, index and array (just like forEach, map, filter, etc.) and returns the value if found, or undefined if not found.

var instructors = [{name: "Elie"}, {name: "Matt"}, {name: "Tim"}, {name: "Colt"}];

instructors.find(function(val){
return val.name === "Tim";
}); // {name: "Tim"}

If you only need the index, rather than writing this function with two parameters and returning the index, there is another method findIndex which just returns the index automatically, or -1 if the item is not found.

instructors.findIndex(function(val){
return val.name === "Tim";
}); // 2

Improvements For Working With Strings

The includes method is more straightforward than using indexOf to look for a value in a string: it returns a boolean if the value is in the string or not. Note that as of ES2016 the includes() method works on arrays as well as strings.

//ES5
"awesome".indexOf("some") > -1 // true

//ES2015
"awesome".includes("some"); // true

Improvements For Working With Numbers

Now there is a simpler way to check if a number is not a number NaN using the isFinite method which is called on the Number constructor:

// ES5
function seeIfNumber(val){
if(typeof val === "number" && !isNaN(val)){
return "It is a number!";
}
}

// ES2015
function seeIfNumber(val){
if(Number.isFinite(val)){
return "It is a number!";
}
}

Similarly you can use Number.isInteger() to check for an integer.

Destructuring Objects & Arrays (& Swap)

ES2015 makes it a bit easier to pull/reference values stored in objects and arrays.

Object Destructuring

Given an object:

var instructor = {
firstName: "Elie",
lastName: "Schoppik"
}

To create variables from the values stored within the object, ES5 code is a bit repetitive:

var firstName = instructor.firstName;
var lastName = instructor.lastName;

firstName; // "Elie"
lastName; // "Schoppik"

With ES2015 we can create the variables with one line of code by wrapping the keys inside curly braces. The variable names must match the key names.

var {firstName, lastName} = instructor;

firstName; // "Elie"
lastName; // "Schoppik"

…BUT if we want to rename the variables to something other than the key names, this is possible:

var {firstName:first, lastName:last} = instructor;

first; // "Elie"
last; // "Schoppik"

This also is useful for creating objects. Rather than writing a createObject function with lots of or statements, we can pass a destructured object into the function as a parameter instead. This way the default will be invoked if nothing is passed to the function, otherwise it will create an object as desired:

// ES5 Way
function createInstructor(options){
var options = options || {};
var name = options.name || {first: "Matt", last:"Lane"}
var isHilarious = options.isHilarious || false;
return [name.first, name.last, isHilarious];
}

// ES6 Way
function createInstructor({name = {first:"Matt", last:"Lane"}, isHilarious=false } = {}){
return [name.first, name.last, isHilarious];
}

// Result either way
createInstructor(); // ["Matt", "Lane", false]
createInstructor({isHilarious:true}); // ["Matt", "Lane", true]
createInstructor({name: {first:"Tim", last:"Garcia"}}); // ["Tim", "Garcia", false]

We can also pass a destructured object as a parameter to a function if we know the key names from the function:

// Given a known object...
var instructor = {
name: "Elie",
favColor: "Purple"
};

// ES5 Way
function displayInfo(obj) {
return [obj.name, obj.favColor];
}

// ES6 Way
function displayInfo({name,favColor}) {
return [name, favColor];
}

// Result either way
displayInfo(instructor); // ["Elie", "Purple"]

Array Destructuring

The setup is similar to working with objects: the variable names go into an array:

// Given an array...
var arr = [1,2,3];

// ES5: each variable is defined individually
var a = arr[0];
var b = arr[1];
var c = arr[2];

//ES2015: accomplished in one line
var [a,b,c] = arr;

// Result either way
a; // 1
b; // 2
c; // 3

Variables can also be assigned to each result of a function when the output is an array:

// Given a function...
function returnNumbers(a,b) {
return [a,b];
}

// ES5: Call the function twice to determine variables:
var first = returnNumbers(5,10)[0];
var second = returnNumbers(5,10)[1];

// ES2015: Call the function one time for both variables:
[first, second] = returnNumbers(5,10);

// Result either way
first; // 5
second; // 10

Swapping

And a huge win!! A temp variable is no longer needed to swap values, instead we can swap directly:

//ES5
function swap(a,b){
var temp = a;
a = b;
b = temp;
return [a,b];
}

// ES2015
function swap(a,b){
return [a,b] = [b,a];
}

// Result either way
swap(10,5); // [5,10]

This example from the class is a weird choice because couldn’t you just return [b, a] from the ES5 function? But anyway, I’ve needed this before so not complaining!

Class Keyword, and Methods

The class data structure is now available in JavaScript as a reserved keyword. The class keyword creates a constant which cannot be redeclared, and it doesn’t hoist so classes should be declared at the top of a file. We can still use new keyword to create objects, now called instances. Example:

// ES5: create a constructor function
function Student(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}

//ES2015: constructor is built with class instead
class Student {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
}

// Either way, create a new object (aka instance) with the new keyword
var nia = new Student('Nia', 'Murrell');

Instance Methods

Note that with class the prototype is abstracted away, and instead, instance methods should be included in the class definition. Under the hood, the method is added to the prototype for you:

// ES5: methods are added to the prototype
function Student(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}

Student.prototype.sayHello = function(){
return "Hello " + this.firstName + " " + this.lastName;
}

// ES2015: methods are included in the class definition!
class Student {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
sayHello(){
return `Hello ${this.firstName} ${this.lastName}`;
}
}

Class Methods, aka Static Methods

If you want to add a method to the class itself rather than to objects created from the class, we use the static keyword. These are called static methods; some other examples are array.isArray, Object.create. To add static methods in ES6:

// ES5 Class Method definition
function Student(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}

Student.prototype.sayHello = function(){
return "Hello " + this.firstName + " " + this.lastName;
}

Student.isStudent = function(obj){
return obj.constructor === Student;
}

// ES2015 Class Method creation with static keyword
class Student {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
sayHello(){
return `Hello ${this.firstName} ${this.lastName}`;
}
static isStudent(obj){
return obj.constructor === Student;
}
}

Prototypal Inheritance - Extends & Super Keywords

We learned about prototypal inheritance in a previous section: this is how you assign methods from one constructor function to another in a way that will also more the this reference to the new object. With ES2015 the new keyword extends make this one step instead of two:

// ES5 Steps
// Create a constructor function
function Person(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}

// ...then add a method to the prototype
Person.prototype.sayHello(){
return "Hello " + this.firstName + " " + this.lastName;
}

// ...then create a second constructor
function Student(firstName, lastName){
Person.apply(this, arguments);
}

// ...then add the Person methods to the Student prototype
// ...THEN assign the dunder proto back to the Student. WHEW!
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

This is simplified in ES2015 with extends, which brings over all of the methods from the first class onto the second. Combined with the super keyword, this allows you to set up one class based on another, transfer the methods to the new class’ prototype, and still reduce code duplication without requiring the apply method to do it:

class Person {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
sayHello(){
return `Hello ${this.firstName} ${this.lastName}`;
}
}

class Student extends Person {
constructor(firstName, lastName){
// you must use super here!
super(firstName, lastName);
}
}

// Note we can still add extra props to the new class if we want to
class Student extends Person {
constructor(firstName, lastName, dorm){
super(firstName, lastName);
this.dorm = dorm;
}
}

// Or we could use the rest operator to simplify further
class Student extends Person {
constructor(){
super(...arguments);
this.dorm = dorm;
}
}

Maps & WeakMaps

Map is another new data structure to ES2015, and they are similar to “hash maps” in other languages. A Map is like an object, except that the keys can be any data type. A WeakMap is similar to a Map, except that all of the keys must be objects and you can’t iterate over them. While WeakMaps are less common, there are several reasons to use a Map:

  • Finding the size is easy - no more loops or Object.keys()
  • You can accidentally overwrite keys on the Object.prototype in an object you make - maps do not have that issue
  • Iterating over keys and values in a map is quite easy as well
  • If you need to look up keys dynamically (they are not hard coded strings)
  • If you need keys that are not strings!
  • If you are frequently adding and removing key/value pairs
  • If you are operating on multiple keys at a time

Maps are created with the new keyword and key-value pairs are altered with set and delete. Unlike an object, you can also get the size() of a Map:

var firstMap = new Map;

firstMap.set(1, 'Elie');
firstMap.set(false, 'a boolean');
firstMap.set('nice', 'a string');
firstMap.delete('nice'); // true
firstMap.size(); // 2

var arrayKey = [];
firstMap.set(arrayKey, [1,2,3,4,5]);

var objectKey = {};
firstMap.set(objectKey, {a:1});

To access the values in a map, the get() method is used. Maps can also be iterated over with forEach and for...of loops:

firstMap.get(1); // 'Elie'
firstMap.get(false); // 'a boolean'
firstMap.get(arrayKey); // [1,2,3,4,5]
firstMap.get(objectKey); // {a:1}

firstMap.forEach(v => console.log(v));

// Elie
// a boolean
// [1,2,3,4,5]
// {a:1}

There is also a built-in iterator for keys and values, or you can access everything together by destructuring an array and using the entries method:

firstMap.values(); // MapIterator of values
firstMap.keys(); // MapIterator of keys

var m = new Map;
m.set(1, 'Elie');
m.set(2, 'Colt');
m.set(3, 'Tim');

for(let [key,value] of m.entries()){
console.log(key, value);
}

// 1 "Elie"
// 2 "Colt"
// 3 "Tim"

Sets & WeakSets

A Set is another new data structure to ES2015; it’s an object in which all values must be unique. So sets are useful when you want to ignore duplicate values, don’t need to access values with keys, or don’t care about the ordering of the values. A WeakSet is similar to a set, except that all of the values must be objects, and they cannot be iterated over.

Sets are also created with the new keyword; they are altered with the add and delete methods. You can also check if a set contains a value with has:

var s = new Set;

// can also be created from an array
var s2 = new Set([3,1,4,1,2,1,5]); // {3,1,4,2,5}

// to add values to a Set
s.add(10); // {10}
s.add(20); // {20, 10}
s.add(10); // {20, 10} --> note the duplicate is not added

s.size; // 2

s.has(10); // true

s.delete(20); // true

s.size; // 1

Sets can be iterated over with a for...of loop:

s2[Symbol.iterator]; // function(){}...
// we can use a for...of loop!

Promises

Promises help run asynchronous code: it’s a promise to return some result one the code has finished running. Promises are created with the new keyword and accept a callback function with 2 parameters: resolve and reject (although you can name it whatever). Each parameter itself is a function which will be run depending on the outcome of the promise. To create an async function:

function displayAtRandomTime(){
return new Promise(function(resolve,reject){
setTimeout(function(){
if(Math.random() > .5) {
resolve('Yes!');
} else {
reject('No!');
}
},1000);
});
}

At call time, the success or failure (aka resolve or reject) functions are called by .then() or .catch() depending on the result:

displayAtRandomTime().then(function(value){
console.log(value);
}).catch(function(error){
console.log(error);
});

Promise.all()

Promises can be chained with multiple .then() functions (aka, a really dumb name, they are thenable), each of which returns a value to the next. When there are multiple promises to be resolved, we can use the Promise.all() method, which accepts an array of promises. This will reject all of the promises once a since promise in the chain is rejected. If it’s going to fail, it should fail quickly!

Likewise, if all of the promises will resolve, it returns an array of the returned values from all of the promises in the order in which they were resolved (even if they are not resolved sequentially…they may not be).

// We write a function that returns a promise via jQuery
function getMovie(title){
return $.getJSON(`https://omdbapi.com?t=${title}&apikey=thewdb`);
}

// We could run the function multiple times
var titanicPromise = getMovie('titanic');
var shrekPromise = getMovie('shrek');
var braveheartPromise = getMovie('braveheart');

// Or we could use Promise.all to get all the values
Promise.all([titanicPromise, shrekPromise, braveheartPromise]).then(function(movies){
return movies.forEach(function(value){
console.log(value.Year);
});
});

// 1997
// 2001
// 1995

Generators

Generators are a new type of function available in ES2015. Normal functions will keep running until something is returned, or there is no more code to run. But with generator functions, execution can be paused and resumed later. This is helpful when there is a time-consuming function and you only need to run parts of it at a time (if you ask me, write smaller modular functions?).

A generator function is created with the star * symbol. When a generator function is invoked, the object it returns has a method next(), and that method
returns another object, which has the keys value and done:

  • value is what is returned from the paused function using the yield keyword
  • done is a boolean which returns true when the function has completed
function* pauseAndReturnValues(num){
for(let i = 0; i < num; i++){
yield i;
}
}

var gen = pauseAndReturnValues(5);

gen.next(); // {value: 0, done: false}
gen.next(); // {value: 1, done: false}
gen.next(); // {value: 2, done: false}
gen.next(); // {value: 3, done: false}
gen.next(); // {value: 4, done: false}
gen.next(); // {value: undefined, done: true}

We can place multiple yield keywords inside of a generator function to pause multiple times:

function* printValues(){
yield "First";
yield "Second";
yield "Third";
}

var g = printValues();
g.next().value; // "First"
g.next().value; // "Second"
g.next().value; // "Third"

Generator functions have a Symbol property so can be iterated over with a for...of loop:

function* pauseAndReturnValues(num){
for(let i = 0; i < num; i++){
yield i;
}
}

for(val of pauseAndReturnValues(3)){
console.log(val);
}

// 0
// 1
// 2

Generators can also be used with asynchronous methods to pause for a promise:

function* getMovieData(movieName){
console.log('starting')
yield $.getJSON(`https://omdbapi.com?t=${movieName}&apikey=thewdb`);
console.log('ending')
}

var movieGetter = getMovieData('titanic');
movieGetter.next().value.then(val => console.log(val));

But note!: This is improved even further in ES2017 with async functions, so newer practices are available. As a result generators are less & less common.

Other Stuff

Next lessons we do ES2016 and ES2017 so this document will get even bigger!!!!!