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.
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');
class Student {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
}
var elie = new Student('Elie', 'Schoppik'); // same as ES5
function Student(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
Student.prototype.sayHello = function(){
return "Hello " + this.firstName + " " + this.lastName;
}
class Student {
constructor(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
}
sayHello(){
return `Hello ${this.firstName} ${this.lastName}`;
}
}
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;
}
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;
}
}
Class methods are created using the static keyword
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;
}
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 {
}
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!
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
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);
}
}
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
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});
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}
maps implement a Symbol.iterator which means we can use a for...of loop!
firstMap.values(); // MapIterator of values
firstMap.keys(); // MapIterator of keys
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"
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
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
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
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!
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
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
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!
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
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);
});
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]
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
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
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);
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!
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!
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!
// 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)
// 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!
// 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() { ... }
// 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"}
var instructors = [{name: "Elie"}, {name: "Matt"}, {name: "Tim"}, {name: "Colt"}];
instructors.findIndex(function(val){
return val.name === "Tim";
}); // 2
Similar to find, but returns an index or -1 if the value is not found
//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:
// 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:
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.
//ES2015
var calculatedNumber = Math.pow(2,4);
calculatedNumber; // 16
//ES2016
var calculatedNumber = 2**4;
calculatedNumber; // 16
From:
To:
//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:
// 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:
"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
"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
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 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)
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!
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 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?
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!
// 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.
// 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!
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!
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 }
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