ES2015, ES2016
and ES2017
Objectives
-
Refactor Object oriented code to use the class, extends and super keywords
-
Understand how to use new data structures in ES2015
-
Refactor asynchronous code using the native Promise constructor and create functions that can pause and resume execution with generators
-
Utilize helpful ES2015 methods for copying objects, converting array-like-objects into arrays and handling issues with NaN.
- Examine two new features to ES2016
- Use new string methods and refactor code using ES2017 async functions
- Introduce the spread and rest operator for objects
class
-
A new reserved keyword provided by ES2015
-
The class keyword creates a constant - can not be redeclared
-
The class keyword is an abstraction of constructor functions and prototypes. JavaScript does not have built in support for object oriented programming
-
The class keyword does not hoist
-
Still use `new` keyword to create objects
function Student(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
var elie = new Student('Elie', 'Schoppik');
ES5 Object Oriented
- create a constructor function
- use the new keyword to create objects
class Student {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
}
var elie = new Student('Elie', 'Schoppik'); // same as ES5
ES2015 Object Oriented
- use the class keyword instead of creating a function
- inside, use a special method constructor which is run when new is used
- use the new keyword to create objects
function Student(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
Student.prototype.sayHello = function(){
return "Hello " + this.firstName + " " + this.lastName;
}
ES5 Instance Methods
Shared methods and properties are placed directly on the function's prototype property
class Student {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
sayHello(){
return `Hello ${this.firstName} ${this.lastName}`;
}
}
ES2015 Instance Methods
- placed inside of class keyword
- no 'function' keyword - similar to object shorthand notation
- under the hood it is placing methods on the prototype object
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;
}
ES5 Class Methods
Class methods are placed directly on the constructor function
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;
}
}
ES2015 Class Methods
Class methods are created using the static keyword
YOUR
TURN
Inheritance
Passing along methods and properties from one class to another
function Person(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.sayHello(){
return "Hello " + this.firstName + " " + this.lastName;
}
function Student(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
ES5 Inheritance
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
-
Set the prototype property of a constructor to be an object created from another prototype property
-
Reset the constructor property on a constructor function
class Person {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
sayHello(){
return `Hello ${this.firstName} ${this.lastName}`;
}
}
class Student extends Person {
}
ES2015 Inheritance
Use the extends keyword
Super
ES5 Refactoring Constructors
function Person(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.sayHello(){
return "Hello " + this.firstName + " " + this.lastName;
}
function Student(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
Notice the duplication in the Student constructor function!
Use Apply
function Person(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
Person.prototype.sayHello(){
return "Hello " + this.firstName + " " + this.lastName;
}
function Student(){
Person.apply(this, arguments);
}
Use call or apply in a constructor function - apply is handy when there are many arguments
ES2015 - super
class Person {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
sayHello(){
return `Hello ${this.firstName} ${this.lastName}`;
}
}
Super can only be used if a method by the same name is implemented in the parent class
class Student extends Person {
constructor(firstName, lastName){
// you must use super here!
super(firstName, lastName);
}
}
YOUR
TURN
- Quickly create constructor functions and prototype methods using the class keyword
- Add class methods using the static keyword
- Implement inheritance using the extends and super keywords
- ES2015 class syntax is an abstraction of using functions and objects!
Recap
Maps
-
Also called "hash maps" in other languages
-
Until ES2015 - objects were replacements for maps
-
Similar to objects, except the keys can be ANY data type!
-
Created using the new keyword
Maps
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
Keys can be any type!
var arrayKey = [];
firstMap.set(arrayKey, [1,2,3,4,5]);
var objectKey = {};
firstMap.set(objectKey, {a:1});
Extracting Values
firstMap.get(1); // 'Elie'
firstMap.get(false); // 'a boolean'
firstMap.get(arrayKey); // [1,2,3,4,5]
firstMap.get(objectKey); // {a:1}
We can easily iterate over the map!
firstMap.forEach(v => console.log(v));
// Elie
// a boolean
// [1,2,3,4,5]
// {a:1}
Iterating over a map
maps implement a Symbol.iterator which means we can use a for...of loop!
firstMap.values(); // MapIterator of values
firstMap.keys(); // MapIterator of keys
Accessing keys and values in a map
we can access everything with .entries() and destructuring!
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"
Why use maps?
-
Finding the size is easy - no more loops or Object.keys()
-
The keys can be any data type!
-
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
When to use a map
- 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
- Are key-value pairs frequently added or removed?
- If you are operating on multiple keys at a time
WeakMap
-
Similar to a map, but all keys MUST be objects
-
Values in a WeakMap can be cleared from memory if there is no reference to them
-
More performant than maps, but can not be iterated over
Sets
-
All values in a set are unique
-
Any type of value can exist in a set
-
Created using the new keyword
-
Exist in quite a few other languages, ES2015 finally brings them to JavaScript
Syntax
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}
s.has(10); // true
s.delete(20); // true
s.size; // 1
s.add(10); // {10}
s.add(20); // {20, 10}
s.add(10); // {20, 10}
s.size; // 2
s2[Symbol.iterator]; // function(){}...
// we can use a for...of loop!
WeakSet
-
Similar to a set, but all values MUST be objects
-
Values in a WeakSet can be cleared from memory if there is no reference to them
-
More performant than sets, but can not be iterated over
YOUR
TURN
Promises
-
A one time guaranteed return of some future value
-
When that value is figured out - the promise is resolved/fulfilled or rejected
-
Friendly way to refactor callback code
-
Libraries have implemented Promises for a while, ES2015 is a little late to the game
Story time
-
You're hungry - so you go to McDonalds
-
You place your order and get a ticket (a promise)
-
After some time, you either get your food and the promise is resolved or you do not get your food and the promise is rejected
-
If you want another order - you need a new Promise!
Where have you seen promises before?
-
jQuery implemented its own version of a promise called a deferred. jQuery version 3 now supports native promises.
-
Many JavaScript libraries and frameworks (Node, Angular) use popular promise libraries like q and bluebird
We can now create our own promises!
- Created using the new keyword
- Every promise constructor accepts a callback function which contains two parameters, resolve and reject
- You can call these parameters whatever you like, resolve and reject are most common
- These parameters are both functions to be run if the promise is resolved or rejected
A simple example
function displayAtRandomTime(){
return new Promise(function(resolve,reject){
setTimeout(function(){
if(Math.random() > .5) {
resolve('Yes!');
} else {
reject('No!');
}
},1000);
});
}
The returned value from a promise will always contain a .then and .catch method which are functions to be executed when the promise is resolved or rejected
displayAtRandomTime().then(function(value){
console.log(value);
}).catch(function(error){
console.log(error);
});
Returning promises
Since a promise always returns something that has a .then (thenable) - we can chain promises together and return values from one promise to another!
var years = [];
$.getJSON('https://omdbapi.com?t=titanic&apikey=thewdb')
.then(function(movie){
years.push(movie.Year);
console.log(years);
})
.then(function(movie){
years.push(movie.Year);
return $.getJSON('https://omdbapi.com?t=shrek&apikey=thewdb');
})
console.log('ALL DONE!');
// "ALL DONE!"
// ["1997", "2001]
Promise.all
-
Accepts an array of promises and resolves all of them or rejects once a single one of the promises has been first rejected (fail fast).
-
If all of the passed-in promises fulfill, Promise.all is fulfilled with an array of the values from the passed-in promises, in the same order as the promises passed in.
-
You may have seen something like this when $.when in jQuery or Q
-
The promises don't resolve sequentially, but Promise.all waits for them
Promise.all
function getMovie(title){
return $.getJSON(`https://omdbapi.com?t=${title}&apikey=thewdb`);
}
var titanicPromise = getMovie('titanic');
var shrekPromise = getMovie('shrek');
var braveheartPromise = getMovie('braveheart');
Promise.all([titanicPromise, shrekPromise, braveheartPromise]).then(function(movies){
return movies.forEach(function(value){
console.log(value.Year);
});
});
// 1997
// 2001
// 1995
Let's make a function that returns a promise
We can now resolve all of the promises using Promise.all
YOUR
TURN
Generators
- A special kind of function which can pause execution and resume at any time
- Created using a *
- When invoked, a generator object is returned to us with the keys of 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
Our first generator
function* pauseAndReturnValues(num){
for(let i = 0; i < num; i++){
yield i;
}
}
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}
var gen = pauseAndReturnValues(5);
Yield Multiple Values
function* printValues(){
yield "First";
yield "Second";
yield "Third";
}
var g = printValues();
g.next().value; // "First"
g.next().value; // "Second"
g.next().value; // "Third"
We can place multiple yield keywords inside of a generator function to pause multiple times!
Iterating over a generator
function* pauseAndReturnValues(num){
for(let i = 0; i < num; i++){
yield i;
}
}
for(val of pauseAndReturnValues(3)){
console.log(val);
}
// 0
// 1
// 2
Since generators implement a Symbol.iterator property we can use a for...of loop!
Async Generators
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));
We can use generators to pause asynchronous code!
The next value returned is a promise so let's resolve it!
Object.assign
// ES5
var o = {name: "Elie"};
var o2 = o;
o2.name = "Tim";
o.name; // "Tim"
// ES2015
var o = {name: "Elie"};
var o2 = Object.assign({},o);
o2.name = "Tim";
o.name; // "Elie"
Create copies of objects without the same reference!
Fixing up with Object.assign (notice the first parameter)
Not a deep clone
// ES2015
var o = {instructors: ["Elie", "Tim"]};
var o2 = Object.assign({},o);
o2.instructors.push("Colt");
o.instructors; // ["Elie", "Tim", "Colt"];
If we have objects inside of the object we are copying - those still have a reference!
Array.from
// ES5
var divs = document.getElementsByTagName("div"); // returns an array-like-object
divs.reduce // undefined, since it is not an actual array
Convert other data types into arrays
How this was done with ES5 - using call
var converted = [].slice.call(divs) // convert the array-like-object into an array
converted.reduce // function reduce() { ... }
Using Array.from
// 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]
Convert array-like-objects into arrays
Convert different types of objects into arrays
var instructors = [{name: "Elie"}, {name: "Matt"}, {name: "Tim"}, {name: "Colt"}];
instructors.find(function(val){
return val.name === "Tim";
}); // {name: "Tim"}
find
- Invoked on arrays
- Accepts a callback with value, index and array (just like forEach, map, filter, etc.)
- Returns the value found or undefined if not found
var instructors = [{name: "Elie"}, {name: "Matt"}, {name: "Tim"}, {name: "Colt"}];
instructors.findIndex(function(val){
return val.name === "Tim";
}); // 2
findIndex
Similar to find, but returns an index or -1 if the value is not found
includes
//ES5
"awesome".indexOf("some") > -1 // true
//ES2015
"awesome".includes("some"); // true
returns a boolean if a value is in a string - easier than using indexOf
From:
To:
Number.isFinite
// 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!";
}
}
A handy way for handling NaN being a typeof number
From:
To:
Recap
-
The map data structure is useful when creating key value pairs and the keys are not strings.
-
Sets are useful for creating unique data sets and do not require key value pairs
-
The ES2015 Promise constructor allows for creating promises and resolving an array of promises with Promise.all
-
Generators are valuable when creating functions or methods that can pause and resume at any time
-
ES2015 provides a few useful methods for converting array like objects into arrays, making shallow copies of objects, and handling issues with NaN and typeof number.
YOUR
TURN
ES2016 + ES2017 Objectives
- Examine two new features to ES2016
- Use new string methods in ES2017
- Understand how to refactor asynchronous code using ES2017 async functions
- Use the spread and rest operator for objects
Exponentiation Operator **
//ES2015
var calculatedNumber = Math.pow(2,4);
calculatedNumber; // 16
//ES2016
var calculatedNumber = 2**4;
calculatedNumber; // 16
From:
To:
Another example
//ES2016
var nums = [1,2,3,4];
var total = 2;
for(let i = 0; i < nums.length; i++){
total **= nums[i];
}
// ES2015
var nums = [1,2,3,4];
var total = 2;
for(let i = 0; i < nums.length; i++){
total = Math.pow(total,nums[i])
}
From:
To:
[].includes
// ES2015
var nums = [1,2,3,4,5];
nums.indexOf(3) > -1; // true
nums.indexOf(44) > -1; // false
// ES2016
var nums = [1,2,3,4,5];
nums.includes(3); // true
nums.includes(44); // false
From:
To:
padStart
"awesome".padStart(10); // " awesome"
"awesome".padStart(10,'!'); // "!!!awesome"
The first parameter is the total length of the new string
The second parameter is what to pad with from the start. The default is an empty space
padEnd
"awesome".padEnd(10,'!'); // "awesome!!!"
The first parameter is the total length of the new string
The second parameter is what to pad with from the end. The default is an empty space
ES2017 Async Functions
A special kind of function that is created using the word async
async function first(){
return "We did it!";
}
first(); // returns a promise
first().then(val => console.log(val)); // "We did it!"
What makes them really special is the await keyword!
The purpose of async functions is to simplify writing asynchronous code, specifically Promises.
Await
await pauses the execution of the async function and is followed by a Promise. The await keyword waits for the promise to resolve, and then resumes the async function's execution and returns the resolved value.
A reserved keyword that can only be using inside async functions
Think of the await keyword like a pause button (similar to yield with generators)
Using await
async function getMovieData(){
console.log("starting!");
var movieData = await $.getJSON('https://omdbapi.com?t=titanic&apikey=thewdb');
// this line does NOT run until the promise is resolved!
console.log("all done!");
console.log(movieData);
}
getMovieData() // logs an object with data about the movie!
Let's write a function that gets some movie data from the OMDB API!
No .then or callback or yield necessary!
Object async
var movieCollector = {
data: "titanic",
async getMovie(){
var response = await $.getJSON(`https://omdbapi.com?t=${this.data}&apikey=thewdb`);
console.log(response);
}
}
movieCollector.getMovie();
We can also place async functions as methods inside objects!
Just make sure to prefix the name of the function with the async keyword
Class async
class MovieData {
constructor(name){
this.name = name;
}
async getMovie(){
var response = await $.getJSON(`https://omdbapi.com?t=${this.name}&apikey=thewdb`);
console.log(response);
}
}
var m = new MovieData('shrek');
m.getMovie();
We can also place async functions as instance methods with es2015 class syntax
But what happens when things go wrong?
Handling errors
async function getUser(user){
try {
var response = await $.getJSON(`https://api.github.com/users/${user}`);
console.log(response.name);
} catch(e){
console.log("User does not exist!");
}
}
If a promise is rejected using await, an error with be thrown so we can easily use a try/catch statement to handle errors!
getUser('elie'); // Elie Schoppik
getUser('foo!!!'); // User does not exist!
Thinking about HTTP Requests
// SEQUENTIAL NOT PARALLEL
async function getMovieData(){
var responseOne = await $.getJSON(`https://omdbapi.com?t=titanic&apikey=thewdb`);
var responseTwo = await $.getJSON(`https://omdbapi.com?t=shrek&apikey=thewdb`);
console.log(responseOne);
console.log(responseTwo);
}
getMovieData();
Below we are making two requests sequentially.
This can really slow down our applications....so how do we fix it?
The second HTTP request does not get made until the first promise is resolved.
Refactoring
// MUCH FASTER!
async function getMovieData(){
var titanicPromise = $.getJSON(`https://omdbapi.com?t=titanic&apikey=thewdb`);
var shrekPromise = $.getJSON(`https://omdbapi.com?t=shrek&apikey=thewdb`);
var titanicData = await titanicPromise;
var shrekData = await shrekPromise;
console.log(titanicData);
console.log(shrekData);
}
getMovieData();
Start the HTTP requests in parallel and then await their resolved promise!
Await with Promise.all
async function getMovieData(first, second){
var moviesList = await Promise.all([
$.getJSON(`https://omdbapi.com?t=${first}&apikey=thewdb`),
$.getJSON(`https://omdbapi.com?t=${second}&apikey=thewdb`)
]);
console.log(moviesList[0].Year);
console.log(moviesList[1].Year);
}
getMovieData('shrek', 'blade');
// 2001
// 1998
We can use Promise.all to await multiple resolved promises
Here we are simply waiting for an array of promises to resolve!
YOUR
TURN
Object Rest
var instructor = {first:"Elie", last:"Schoppik", job:"Instructor", numSiblings:3};
Gather remaining (rest) of keys and values in an object and create a new one out of them
var { first, last, ...data } = instructor
first; // "Elie"
last; // "Schoppik"
data; // { job: "Instructor", numSiblings: 3 }
Object Spread
var instructor = {first:"Elie", last:"Schoppik", job:"Instructor"};
var instructor2 = {...instructor, first:"Tim", last:"Garcia"};
Quite common in React and Redux
var defaults = {job: "Instructor", ownsCat:true, ownsDog: true};
var matt = {...defaults, ownsCat: false};
var colt = {...defaults, ownsDog: false};
Great for creating objects starting with default values and is a more concise alternative to Object.assign
Spread out keys and values from one object to another
Recap
- ES2016 provides the ** operator and [].includes
- ES2017 provides helpful string methods and introduces async functions
- The async/await keywords in ES2017 allow for writing synchronous looking functions that under the hood are asynchronous
- We can combine async functions with Promise.all to create readable synchronous "looking" code
- The rest and spread operator are proposed changes to JavaScript
ES2015, 2016, 2017 Part 2
By Elie Schoppik
ES2015, 2016, 2017 Part 2
ES2015, 2016, 2017 Part 2
- 4,126