ES2015

ES2015 Background

  • ES2015: prevously known as ES6
  • ES stands ECMA Script
  • ECMA stands for European Computer Manufacturers Association
  • ECMA Script (ES) and a standard/protocol. JavaScripts is an implementatation

ES2015 Additions

  • let, const (can't redeclare, hoisting, block scope)
  • template strings (string interpolation, multi-line strings)
  • arrow functions (no own this and arguments)
  • default parameters
  • for...of loops (array, map, set)
  • rest and spread operators (... returns array, spread vs apply)
  • object shorthand notation
  • computed property names
  • object destructuring
  • array destructuring
  • class keyword
  • super and extends keywords
  • Maps / Sets
  • Promises
  • Generators
  • Object, Number, Array method (Object.assign, Array.from, find, findIncdex, includes, Number.isFinite)

const

const: declare constant

  • You are not able to change the value of primitives (imutable)
  • You change value of array and objects (mutable),
  • However, you cannot re-declare constant with the same name for both primitives and objects
  • primitive refers to string, numbers, boolean, null, undefined, symbol (new in ES2015)
In [1]:
const numbers = [1,2,3,4];
numbers.push(5);
numbers;
Out[1]:
[ 1, 2, 3, 4, 5 ]
In [2]:
// numbers = "no!" //TypeError

let

let: create a new kind of scope besides global and function

  • Use 'let' when
    • you're working inside of a block and don't want variables defined inside of that blcok to be accessible outside.
    • you're working with an asyn function
  • Other keywords in Javascript which creates blocks (scope) for us: if, for, while, do, try, catch, finally
  • You cannot redeclare the same varaible using 'let'
  • When we use 'let' inside of those blocks, we create our own kind of scope
  • Hoisting
    • variable defined using 'var' keyword will have their variable declarations lifted to the top of scope that they're at
  • 'let' does hoist (lifted), but it is in a TDZ (Temporal Dead Zone) so we cannot access it

Global scope

var global = 1; global //1

Function scope

In [3]:
function test(){
  var fnVariable = "secret";
}

test //undefined
Out[3]:
[Function: test]
In [4]:
// fnVariable //Error: fnVariable is not defined

Hoisting

In [5]:
function helloInstructor(){
  return elie;
  var elie = "Me!";
}
helloInstructor(); //undefined
In [6]:
function helloSecondInstructor(){
  return colt;
  let colt = "HIM!";
}
// helloSecondInstructor(); //ReferenceError

Common Use Case for 'let'

In [8]:
// for(var i = 0; i < 5; i++){
//   setTimeout(function(){
//     console.log(i);
//   },1000)
// }

By the time the setTimeout runs, the for loop has already finished running and the value of i has been incremented to 5 and the loop has stopped. Only then does setTimeOut run

Before the 'let' keyword , the solution to this was to run another function inside the loop and invoke it immediately so that each setTimeout will have its own value of i.

In [9]:
// for(var i = 0; i < 5; i++){
//   (function(j){
//     setTimeout(function(){
//       console.log(j);
//     },1000);
//   })(i)
// }

// 0
// 1
// 2
// 3

// 4

Immediately-invoked function expression (IIFE) (tricky)

Function expression

In [11]:
// functionOne(); //Output: undefined is not a function
In [12]:
var functionOne = function() {
  console.log("Hello!");
};

Function declaration

In [13]:
functionTwo(); // Output: "Hello!"

function functionTwo() {
  console.log("Hello!");
}
Hello!

Refactor with 'let'

  • let allows us to declare variables that are limited in scope to the block and a new variable is created for each iteration in the loop
In [15]:
// for(let i = 0; i < 5; i++){
//   setTimeout(function(){
//     console.log(i);
//   },1000);
// }

// 0
// 1
// 2
// 3
// 4

Template Strings

  • Enable you to concatenate strings easily
  • Enable you to write multiline strings
  • Remove + and "" from string expression
  • Use `` to enclose string expression

Example: Concatenate Strings

In [16]:
var firstName = "Elie";
var lastName = "Schoppik";

console.log(`Hello${firstName}${lastName}`);
HelloElieSchoppik
In [17]:
console.log(`Hello ${firstName} ${lastName}`);
Hello Elie Schoppik

Example: Multi-Line Strings

In [18]:
/*output: syntax error
"
Hello
"
*/

It works!

In [19]:
`
Hello
Elie!
`
Out[19]:
'\nHello\nElie!\n'

Arrow Functions

  • Enable you to write simplier function expression 1) Multi-line arrow function
    • Remove keyword 'function', add '=>' after parameters, if with function name, add '=' before function name a) for named function:
        - from: `function fn(a,b) {}`
        - to    : `fn = (a,b) => {}`
      
      b) for anonymous function
        - from: `function (a,b) {}`
        - to    : `() => {}`
      
      2) One-line arrow function
    • all of 1) plus omit the return keyword as well as curly braces
  • Do not get their own keywords this and 'arguments' (use rest instead) (tricky)
  • Should NEVER be used as methods in objects since we will get the incorrect value of the keyword this.
    • unless the arrow function is inside of another function

Example: Arrow Functions with Function Expression

ES5

In [20]:
var add = function(a,b){
  return a+b;
}

ES2015

  • replace the keyword 'function' with '=>''
  • add a "=>" after the parameters
In [21]:
var add = (a,b) => {
  return a+b;
}

Example: One-line Arrow Functions: Example 1

Continue from example above

In [22]:
var add = (a,b) => a+b;

One-line Arrow Functions: Example 2 (map)

ES5

In [24]:
[1,2,3].map(function(value){
    return value * 2;
});
Out[24]:
[ 2, 4, 6 ]

ES2015

In [25]:
[1,2,3].map(value => value * 2);
Out[25]:
[ 2, 4, 6 ]

Example: One-line Arrow Functions: Example 3 (map & filter)

Before ES2015

In [26]:
function doubleAndFilter(arr){
    return arr.map(function(value){
        return value * 2;
    }).filter(function(num){
        return num % 3 === 0;
    })
};
doubleAndFilter([5,10,15,20]);
Out[26]:
[ 30 ]

ES2015

  • Single parameter, no need to wrap in parenthesis
In [27]:
var doubleAndFilter = arr => arr.map(val => val * 2).filter(num => num % 3 === 0);
doubleAndFilter([5,10,15,20]);
Out[27]:
[ 30 ]

Problem in Arrow Functions: 'this' Arrow functions do not have their own keyword this

In [31]:
var instructor = {
  firstName: "Elie",
  
   //this refer to the enclosing context i.e. global object
  sayHi: () => 'Hello ${this.firstName}'
}

instructor.sayHi();
Out[31]:
'Hello ${this.firstName}'

Using 'this' with Arrow Functions

Before ES2015: use bind

In [32]:
var instructor = {
  firstName: "Elie",
  sayHi: function(){
    setTimeout(function(){
      console.log("Hello " + this.firstName);
    }.bind(this), 1000);
  }
}

instructor.sayHi();
Hello Elie

ES2015

  • Why does this arrow function work? It's inside the setTimeOut function
  • The keyword this refers to the enclosing context (the instructor object).
In [33]:
var instructor = {
    firstName: "Elie",
    sayHi: function(){
        setTimeout(() => {
            console.log("Hello " + this.firstName);
        }, 1000);
    }
}

instructor.sayHi();
Hello Elie

We used both the function keyword and an arrow function - why? Can we use arrow function for the sayHi method as well? If we use an arrow function on the sayHi method, the sayHi method will not not have its own keyword this and the keyword this refers to the enclosing context (i.e. the global object - window)

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

instructor.sayHi();
Hello Elie

Using 'arguments' with Arrow Functions Arrow functions do not have their own keyword 'arguments'

In [36]:
// var add = (a,b) => {
//   return arguments;
// }

// add(2,4); // ReferenceError: arguments is not defined

If the arrow function is inside of another function, it will be the outer function arguments

In [37]:
function outer() {
  return innerFunction = () => {
    return arguments;
  }
}

outer(1)(2);
Out[37]:
{ '0': 1 }

Exercise Arrow Functions

1 - Refactor the following code to use ES2015 one-line arrow functions - make sure your function is also called tripleAndFilter

function tripleAndFilter(arr){
  return arr.map(function(value){
    return value * 3;
  }).filter(function(value){
    return value % 5 === 0;
  })
}

Can't find variable xxx if let is not used

In [38]:
let tripleAndFilter = arr => arr.map(value => value * 3).filter(value => value % 5 === 0);

2 - Refactor the following code to use ES2015 one-line arrow functions. Make sure your function is also called doubleOddNumbers

function doubleOddNumbers(arr){
  return arr.filter(function(val){
    return val % 2 !== 0;
  }).map(function(val){
    return val *2;
  })
}
In [39]:
let doubleOddNumbers = arr => arr.filter(val => val % 2 !== 0).map(val => val *2);

3 - Refactor the following code to use ES2015 arrow functions. Make sure your function is also called mapFilterAndReduce.

function mapFilterAndReduce(arr){
  return arr.map(function(val){
    return val.firstName
  }).filter(function(val){
    return val.length < 5;
  }).reduce(function(acc,next){
    acc[next] = next.length
    return acc;
  }, {})
}
In [40]:
let mapFilterAndReduce = (arr) => arr.map(val => val.firstName).filter(val => val.length < 5).reduce((acc,next) => {
  acc[next] = next.length;
  return acc;
}, {});

4 - Write a function called createStudentObj which accepts two parameters, firstName and lastName and returns an object with the keys of firstName and lastName with the values as the parameters passed to the function.

Example:

createStudentObj('Elie', 'Schoppik') // {firstName: 'Elie', lastName: 'Schoppik'}

Pre ES2015

In [44]:
// function createStudentObj(firstName, lastName){
//   return {firstName: firstName, lastName: lastName};
// }

ES2015

In [47]:
// let createStudentObj = (firstName, lastName) => ({firstName: firstName, lastName: lastName});

5 - Given the following code: Refactor this code to use arrow functions to make sure that in 1000 milliseconds you console.log 'Hello Colt'

var instructor = {
      firstName: "Colt",
      sayHi: function(){
        setTimeout(function(){
          console.log('Hello ' + this.firstName)
        },1000)
      }
    }
In [48]:
var instructor = {
  firstName: "Colt",
  sayHi: function(){
    setTimeout(() => console.log('Hello ' + this.firstName), 1000);
  }
}

Default Parameters

  • We can set anything to be the default value numbers, strings, booleans, arrays, objects, even function

Before ES2015

In [49]:
function add(a, b){
  return a+b;
}

add(); // NaN because a is undefined and b is undefined
Out[49]:
NaN

ES2015

In [51]:
function add(a=10, b=20){
  return a+b;
}

add();
Out[51]:
30
In [52]:
add(20);
Out[52]:
40

for...of loops

  • Use with Arrays, Maps and Sets (news data structures in ES2015)
  • Syntax is very similar to for...in loop
  • Can't access an index
  • Can only be used on data structures with a Symbol.iterator method implemented (no objects!)
    • e.g. look in array - console.dir([]): found under __proto__ or
    • look in constructor function - dir(Array): found under "prototype"
  • Can't use with objects. To iterate objects, use for...in loop instead
    • for...in loop is traditionally used to loop over keys in an object
In [53]:
var arr = [1,2,3,4,5];

for(let val of arr){
  console.log(val);
}
1
2
3
4
5

Rest operator

  • denotes by ...
  • used in a parameters to a function
    • Collects the remaining arguments in a function and return to us in an array

ES5

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

A little fancier ES5

In [55]:
function sumArguments(){
  var argumentsArray = [].slice.call(arguments);
  return argumentsArray.reduce(function(accumulator,nextValue){
    return accumulator + nextValue;
  });
}

ES2015

In [56]:
function sumArguments(...args){
  return args.reduce((acc, next) => acc + next);
}

ES2015 (simpler)

In [57]:
var sumArguments = (...args) => args.reduce((acc, next) => acc + next);

Spread operator

  • Denotes by ...
  • Used outside of parameters to a function
  • Used on arrays to spread each value out (as a comma separated value) convert [1,2] to (1,2)
  • Useful when you have an array, but what you are working with expects comma separated values (CSV)

Spread and CSV ES5

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

Difference between + and concat on arrays

In [60]:
var combined = arr1.concat(arr2).concat(arr3);
combined;
Out[60]:
[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
In [61]:
var combined = arr1 + arr2 + arr3;
combined
Out[61]:
'1,2,34,5,67,8,9'

ES2015

In [63]:
var combined = [...arr1, ...arr2, ...arr3];
combined
Out[63]:
[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

Spread instead of Apply

In [64]:
var arr = [3,2,4,1,5];
Math.max(arr); // NaN
Out[64]:
NaN

ES5

In [65]:
Math.max.apply(this, arr);
Out[65]:
5

ES2015

In [66]:
Math.max(...arr);
Out[66]:
5

How can we invoke sumValues as a function using nums as a parameter?

In [67]:
function sumValues(a,b,c){
  return a+b+c;
}

var nums = [12,15,20];

ES5

In [68]:
sumValues.apply(this, nums);
Out[68]:
47

ES2015

In [69]:
sumValues(...nums);
Out[69]:
47

Exercise: Rest and Spread Write a function called smallestValue which accepts a variable number of parameters and returns the smallest parameters passed to the function.

Examples:

smallestValue(4,1,12,0) // 0
smallestValue(5,4,1,121) // 1
smallestValue(4,2) // 2
smallestValue(99,12321,12.2) // 2
In [70]:
function smallestValue(...args){
  return Math.min(...args);
}

Write a function called placeInMiddle which accepts two parameters, an array and another array. This function should return the first array with all of the values in the second array placed in the middle of the first array.

Examples:

placeInMiddle([1,2,6,7],[3,4,5]) // [1,2,3,4,5,6,7]
placeInMiddle([1],[3,4,5]) // [3,4,5,1]
placeInMiddle([1,6],[2,3,4,5]) // [1,2,3,4,5,6]
placeInMiddle([],[2,3,4,5]) // [2,3,4,5]
In [72]:
function placeInMiddle(arr, vals){
  return [...arr.slice(0, arr.length/2), ...vals, ...arr.slice(arr.length/2)];
}

Alternative

In [73]:
function placeInMiddle(arr, vals){
  let mid = Math.floor(arr.length/2)
  arr.splice(mid,0,...vals)
  return arr;
}

Write a function called joinArrays which accepts a variable number of parameters (you can assume that each argument to this function will be an array) and returns an array of all of the parameters concatenated together

Examples:

joinArrays([1],[2],[3]) // [1,2,3]
joinArrays([1],[2],[3],[1],[2],[3]) // [1,2,3,1,2,3]
joinArrays([1,2,3],[4,5,6],[7,8,9]) // [1,2,3,4,5,6,7,8,9]
joinArrays([1],[3],[0],[7]) // [1,3,0,7]
In [74]:
function joinArrays(...args){
  return args.reduce((acc, next) => acc.concat(next), [])
}

Write a function called sumEvenArgs which takes all of the parameters passed to a function and returns the sum of the even ones.

Examples:

sumEvenArgs(1,2,3,4) // 6
sumEvenArgs(1,2,6) // 8
sumEvenArgs(1,2) // 2
In [75]:
function sumEvenArgs(...args){
  return args.reduce((acc, next) => next % 2 === 0 ? acc += next : acc, 0)
}

Write a function called flip which accepts a function and a value for the keyword this. Flip should return a new function that when invoked, will invoke the function passed to flip with the correct value of the keyword this and all of the parameters passed to the function REVERSED. HINT - if you pass more than two parameters to flip, those parameters should be included as parameters to the inner function when it is invoked. You will have to make use of closure!

Examples: Example 1

function personSubtract(a,b,c){
  return this.firstName + " subtracts " + (a-b-c);
}

var person = {
  firstName: 'Elie'
}

var flipFn = flip(personSubtract, person);
flipFn(3,2,1) // "Elie subtracts -4"

var flipFn2 = flip(personSubtract, person, 5,6);
flipFn(7,8). // "Elie subtracts -4"

Example 2

function subtractFourNumbers(a,b,c,d){
    return a-b-c-d;
}
flip(subtractFourNumbers,this,1)(2,3,4) // -2
flip(subtractFourNumbers,this,1,2)(3,4) // -2
flip(subtractFourNumbers,this,1,2,3)(4) // -2
flip(subtractFourNumbers,this,1,2,3,4)() // -2
flip(subtractFourNumbers,this)(1,2,3,4) // -2
flip(subtractFourNumbers,this,1,2,3)(4,5,6,7) // -2
flip(subtractFourNumbers,this)(1,2,3,4,5,6,7,8,9,10) // -2
flip(subtractFourNumbers,this,11,12,13,14,15)(1,2,3,4,5,6,7,8,9,10) // -22
In [76]:
function flip(fn, thisArg, ...outerArgs){
  return function(...innerArgs){
    let allArgs = outerArgs.concat(innerArgs).slice(0, fn.length);
    return fn.apply(thisArg, allArgs.reverse());
  }
}

Write a function called bind which accepts a function and a value for the keyword this. Bind should return a new function that when invoked, will invoke the function passed to bind with the correct value of the keyword this. HINT - if you pass more than two parameters to bind, those parameters should be included as parameters to the inner function when it is invoked. You will have to make use of closure!

Example 1:

function firstNameFavoriteColor(favoriteColor){
  return this.firstName + "'s favorite color is " + favoriteColor
}

var person = {
  firstName: 'Elie'
}

var bindFn = bind(firstNameFavoriteColor, person);
bindFn('green') // "Elie's favorite color is green"

var bindFn2 = bind(firstNameFavoriteColor, person, 'blue');
bindFn2('green') // "Elie's favorite color is blue"

Example 2:

function addFourNumbers(a,b,c,d){
    return a+b+c+d;
}
bind(addFourNumbers,this,1)(2,3,4) // 10
bind(addFourNumbers,this,1,2)(3,4) // 10
bind(addFourNumbers,this,1,2,3)(4) // 10
bind(addFourNumbers,this,1,2,3,4)() // 10
bind(addFourNumbers,this)(1,2,3,4) // 10
bind(addFourNumbers,this)(1,2,3,4,5,6,7,8,9,10) // 10
In [77]:
function bind(fn, thisArg, ...outerArgs){
  return function(...innerArgs){
    return fn.apply(thisArg, [...outerArgs, ...innerArgs]);
  }
}

Object Enhancements

1) Object Shorthand Notation

  • if key and values have the same name, we do not have to repeat that declaration

2) Object Methods

  • omit the function keyword and place () after the name of the method

3) Computed Property Names

  • assign a value using bracket notation while defining our object
  • add brackets around the name of the key to let Javascript compute the name of the property

Object Shorthand Notation

In [78]:
var firstName = "Elie";
var lastName = "Schoppik";

ES5

In [79]:
var instructor = {
  firstName: firstName,
  lastName: lastName
}

ES2015

  • if key and values have the same name, we do not have to repeat that declaration
In [80]:
var instructor = {
    firstName,
    lastName
}

Object Method

ES5

In [81]:
var instructor = {
  sayHello: function(){
    return "Hello!";
  }
}

ES2015

  • Do NOT use arrow functions here!
  • Omit the 'function' keyword and place () after the name of the method
In [82]:
var instructor = {
  sayHello(){
    return "Hello!";
  }
}

Computed Property Names

ES5

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

instructor.Elie;
Out[83]:
'That\'s me!'

ES2015

  • assign a value using bracket notation while defining an object at the same time
In [84]:
var firstName = "Elie";
var instructor = {
  // add brackets around the name of the key to let Javascript compute the name of the property
  [firstName]: "That's me!"
}

instructor.Elie;
Out[84]:
'That\'s me!'

Object Destructuring

  • Object destructuring is an idea of extracting values from properties stored in objects into distinct variables
  • You have to name the variables the same exact names as the keys in the object
  • If you don't want to name the variables with the same names as the key in the object, you can simply add a colon and a new variable name
  • Setting default values
    • We're passing in a destructured object as a default parameter!
    • We assign as a default value an empty object so ES2015 knows we are destructuring function ({name = first:firstname} = {}) {}; //think reverse
    • If nothing is passed in, we default to the destructured object as the parameterpass a destructured object as a parameter to a function
  • Object field as parameters
    • very common in React

Exatract Values

In [85]:
var instructor = {
  firstName: "Elie",
  lastName: "Schoppik"
}

ES5

In [86]:
var firstName = instructor.firstName;
var lastName = instructor.lastName;

firstName;
Out[86]:
'Elie'
In [87]:
lastName;
Out[87]:
'Schoppik'

ES2015

  • You have to name the variables the same exact names as the keys in the object
In [88]:
var {firstName, lastName} = instructor;

firstName;
Out[88]:
'Elie'
In [89]:
lastName;
Out[89]:
'Schoppik'

If you don't want to name the variables with the same names as the key in the object, you can simply add a colon and a new variable name

In [90]:
var {firstName: first, lastName:last} = instructor;

first;
Out[90]:
'Elie'
In [91]:
last;
Out[91]:
'Schoppik'

Default Vales with an Object

ES5

In [94]:
function createInstructor(options){
  var options = options || {}; //if options parameters not passed in, assign it to an empty object
  var name = options.name || {first: "Matt", last:"Lane"}
  var isHilarious = options.isHilarious || false;
  return [name.first, name.last, isHilarious];
}

createInstructor();
Out[94]:
[ 'Matt', 'Lane', false ]
In [95]:
createInstructor({isHilarious:true});
Out[95]:
[ 'Matt', 'Lane', true ]
In [96]:
createInstructor({name: {first:"Tim", last:"Garcia"}});
Out[96]:
[ 'Tim', 'Garcia', false ]

ES2015

  • We're passing in a destructured object as a default parameter!
  • We assign as a default value an empty object so ES2015 knows we are destructuring
  • If nothing is passed in, we default to the destructured object as the parameter
In [97]:
function createInstructor({name = {first:"Matt", last:"Lane"}, isHilarious=false } = {}){
  return [name.first, name.last, isHilarious];
}

createInstructor();
Out[97]:
[ 'Matt', 'Lane', false ]
In [98]:
createInstructor({isHilarious:true});
Out[98]:
[ 'Matt', 'Lane', true ]
In [100]:
createInstructor({name: {first:"Tim", last:"Garcia"}});
Out[100]:
[ 'Tim', 'Garcia', false ]

Object fields as parameters

ES5

In [101]:
function displayInfo(obj) {
  return [obj.name, obj.favColor];
}

var instructor = {
  name: "Elie",
  favColor: "Purple"
};

displayInfo(instructor);
Out[101]:
[ 'Elie', 'Purple' ]

ES2015

In [102]:
function displayInfo({name, favColor}) {
  return [name, favColor];
}

var instructor = {
  name: "Elie",
  favColor: "Purple"
};

displayInfo(instructor);
Out[102]:
[ 'Elie', 'Purple' ]

Array Destructuring

  • Array destruturing allows us to extract values from an array into distinct variables
  • Same as object destructuring, put [] on the left side and arr on the right side (reverse assignment)
In [103]:
var arr = [1,2,3];

ES5

In [105]:
var a = arr[0];
var b = arr[1];
var c = arr[2];

a;
Out[105]:
1
In [106]:
b;
Out[106]:
2
In [107]:
c;
Out[107]:
3

ES2015

In [108]:
var arr = [1,2,3];
var [a,b,c] = arr;

a;
Out[108]:
1
In [109]:
b;
Out[109]:
2
In [110]:
c;
Out[110]:
3
In [111]:
function returnNumbers(a,b) {
  return [a,b];
}

ES5

In [112]:
var first = returnNumbers(5,10)[0];
var second = returnNumbers(5,10)[1];

first;
Out[112]:
5
In [113]:
second;
Out[113]:
10

ES2015

In [114]:
var [first, second] = returnNumbers(5,10);

first;
Out[114]:
5
In [115]:
second;
Out[115]:
10

Swapping Values

ES5

In [116]:
function swap(a,b){
  var temp = a;
  a = b;
  b = temp;
  return [a,b];
}

swap(10,5);
Out[116]:
[ 5, 10 ]

ES2015

In [118]:
function swap(a,b){
  [a,b] = [b,a];
  return [a,b]
}

swap(10,5);
Out[118]:
[ 5, 10 ]

Exercise: Destructuring

Write a function called displayStudentInfo which accepts an object and returns the string "Your full name is" concatenated with the value of the first key and a space and then the value of the last key. See if you can destructure this object inside of the function.

Examples:

displayStudentInfo({first: 'Elie', last:'Schoppik'}) // 'Your full name is Elie Schoppik')
In [119]:
function displayStudentInfo(obj){
  var {first, last} = obj;
  return `Your full name is ${first} ${last}`;
}

Write a function called printFullName which accepts an object and returns the string "Your full name is" concatenated with the value of the first key and a space and then the value of the last key. See if you can destructure this object DIRECTLY from the parameters. The output of the printFullName function should be the exact same as the displayStudentInfo function.

Examples:

printFullName({first: 'Elie', last:'Schoppik'}) // 'Your full name is Elie Schoppik'

You will have to pass in the correct parameters for this function!

In [120]:
function printFullName({first, last} = {}){
  return `Your full name is ${first} ${last}`;
}

Write a function called createStudent which accepts as a parameter, a default parameter which is a destructured object with the key of likesES2015 and value of true, and key of likesJavaScript and value of true.

If both the values of likesJavaScript and likesES2015 are true, the function should return the string 'The student likes JavaScript and ES2015'. If the value of likesES2015 is false the function should return the string 'The student likes JavaScript!' If the value of likesJavaScript is false the function should return the string 'The student likesES2015!' If both the value of likesJavaScript and likesES2015 are false, the function should return the string 'The student does not like much...'

Examples:

createStudent() // 'The student likes JavaScript and ES2015')
createStudent({likesES2015:false}) // 'The student likes JavaScript!')
createStudent({likesJavaScript:false}) // 'The student likes ES2015!')
createStudent({likesJavaScript:false, likesES2015:false}) // 'The student does not like much...')

You will have to pass in the correct parameters for this function!

In [121]:
function createStudent({likesES2015=true, likesJavaScript=true} = {}){
  if (likesJavaScript && likesES2015){
    return 'The student likes JavaScript and ES2015!';
  }

  if (likesES2015){
    return 'The student likes ES2015!';
  }

  if (likesJavaScript){
    return 'The student likes JavaScript!';
  }

  return 'The student does not like much...';
}

Write a function called reverseArray which accepts an array and returns the array with all values reversed. See if you can do this without creating a new array! (tricky)

Examples:

reverseArray([1,2,3,4,5]) // [5,4,3,2,1]
reverseArray([1,2]) // [2,1]
reverseArray([]) // []
reverseArray([1,2,3,4,5,6,7,8,9,10]) // [10,9,8,7,6,5,4,3,2,1]
In [122]:
function reverseArray(arr){
  for(var i = 0; i < arr.length/2; i++){
    [arr[i], arr[arr.length - 1 - i]] = [arr[arr.length - 1 - i], arr[i]];
  }
  return arr;
}

Alternative

In [123]:
function reverseArray(arr){
  return arr.reverse();
}

Guess Password Refactoring

  • Refactoring means restructuring existing code without changing its external behavior 1) use let and const variables
  • make sure we're using the let keyword inside our loops 2) use template strings 3) use arrow function
  • on callback function and if possible, refactor code on one line 4) rest 5) use destructuring to swap elements

Element.classList https://developer.mozilla.org/en-US/docs/Web/API/Element/classList

'class' keyword

  • 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
    • make sure you declare your class at the top of the file
  • Still use new keyword to create object

ES5

  • create a constructor function
  • use the new keyword to create object
In [126]:
// function Student(firstName, lastName){
//   this.firstName = firstName;
//   this.lastName = lastName;
// }

// var elie = new Student('Elie', 'Schoppik');

ES2015

  • 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 object
In [128]:
// class Student {
//   constructor(firstName, lastName){
//     this.firstName = firstName;
//     this.lastName = lastName;
//   }
// }

// var elie = new Student('Elie', 'Schoppik'); // same as ES5

Instance Methods

ES5

  • Shared methods and properties are placed directly on the function's prototype property
In [3]:
// function Student(firstName, lastName){
//   this.firstName = firstName;
//   this.lastName = lastName;
// }

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

ES2015

  • placed inside of class keyword
  • no 'function' keyword - similar to object shorthand notation
  • under the hood it is placing methods on the prototype object
In [6]:
// class Student {
//   constructor(firstName, lastName){
//     this.firstName = firstName;
//     this.lastName = lastName;
//   }
//   sayHello(){
//     return `Hello ${this.firstName} ${this.lastName}`;
//   }
// }

Class Methods

  • aka static methods
  • Class methods are created using the 'static' keyword
  • why are they useful?
    • sometimes we don't want every object created from a class to have its own method, specifically when we want to use the method without creating objects from that class e.g. Array.isArray(), Object.create(), Object.freeze(), Object.assign (ES2015), Array.from (ES2015), Number.isFinite (ES2015) (see below)

ES5 Class methods are placed directly on the constructor function

In [2]:
// function Student(firstName, lastName){
//   this.firstName = firstName;
//   this.lastName = lastName;
// }

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

ES2015

  • Class methods are created using the static keyword
In [1]:
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;
  }
}

Usage

In [3]:
var s = new Student('E', 'S');
Student.isStudent(s);
Out[3]:
true
In [4]:
Student.isStudent(s);
Out[4]:
true

Static Method Example: Array.isArray()*

In [6]:
typeof [];
Out[6]:
'object'
In [7]:
Array.isArray([]);
Out[7]:
true

Inheritance

  • Passing along methods and properties from one class to another
  • Implement inheritance using the 'extend' and 'super' keywords
  • check
    • Child.prototype.methodName // returns function definition
    • Child.prototype.constructor === Child // true

ES5 Inheritance Example: Passing the method sayHello() from Person to Student

function Person(firstName, lastName){
  this.firstName = firstName;
  this.lastName = lastName;
}

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

function Student(firstName, lastName){
  this.firstName = firstName;
  this.lastName = lastName;
}

Set the prototype property of a constructor to be an object created from another prototype property

Student.prototype = Object.create(Person.prototype);
// Reset the constructor property on a constructor function
Student.prototype.constructor = Student;

ES2015 Inheritance

In [8]:
class Person {
  constructor(firstName, lastName){
      this.firstName = firstName;
      this.lastName = lastName;
  }
  sayHello(){
      return `Hello ${this.firstName} ${this.lastName}`;
  }
}

Use the 'extends' keyword

In [9]:
class Student extends Person {

}
evalmachine.<anonymous>:1
class Student extends Person {
^

SyntaxError: Identifier 'Student' has already been declared
    at evalmachine.<anonymous>:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at Object.runInThisContext (vm.js:139:38)
    at run ([eval]:1002:15)
    at onRunRequest ([eval]:829:18)
    at onMessage ([eval]:789:13)
    at emitTwo (events.js:126:13)
    at process.emit (events.js:214:7)
    at emit (internal/child_process.js:772:12)
    at _combinedTickCallback (internal/process/next_tick.js:141:11)

Check if Student have method sayHello

In [11]:
Student.prototype.sayHello;
Out[11]:
[Function: sayHello]

Check if Student has its own constructor

In [12]:
Student.prototype.constructor === Student;
Out[12]:
true

Keyword 'super'

  • Super can only be used if a method or property by the same name is implemented in the parent class
  • usage: super([arg]); // calls the parent constructor

ES5

  • Use call or apply in a constructor function - apply is handy when there are many arguments
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 apply in ES5
}

ES2015

In [13]:
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){
    super(firstName, lastName); // use super in ES2015
  }
}
evalmachine.<anonymous>:1
class Person {
^

SyntaxError: Identifier 'Person' has already been declared
    at evalmachine.<anonymous>:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:50:33)
    at Object.runInThisContext (vm.js:139:38)
    at run ([eval]:1002:15)
    at onRunRequest ([eval]:829:18)
    at onMessage ([eval]:789:13)
    at emitTwo (events.js:126:13)
    at process.emit (events.js:214:7)
    at emit (internal/child_process.js:772:12)
    at _combinedTickCallback (internal/process/next_tick.js:141:11)

Exercise: Inheritance and Super

1 - Create a class for for a Vehicle. Each vehicle should have a make, model and year property.

2 - Add an instance method called start which returns the string "VROOM!"

3 - Add an instance method called toString which returns the string "The make, model, and year are" concatenated with the make, model and year property

Examples

var vehicle = new Vehicle("Tractor", "John Deere", 1999)
vehicle.toString() // 'The make, model, and year are Tractor John Deere 1999'
In [14]:
class Vehicle {
  constructor(make, model, year){
    this.make = make;
    this.model = model;
    this.year = year;
  }
  start(){
    return 'VROOM!';
  }
  toString(){
    return `The make, model, and year are ${this.make} ${this.model} ${this.year}`;
  }
}

4 - Create a class for a Car. Each object created from the Car function should also have a make, model, and year and a property called numWheels which should be 4. The Car prototype should inherit all of the methods from the Vehicle prototype

In [15]:
class Car extends Vehicle {
  constructor(){
    super(...arguments) // rest
    this.numWheels = 4;
  }
}

5 - Create a class for a Motorcycle. Each object created from the Motorcycle function should also have a make, model, and year and a property called numWheels which should be 2. The Motorcycle prototype should inherit all of the methods from the Vehicle prototype

In [16]:
class Motorcycle extends Vehicle {
  constructor(){
    super(...arguments) // rest
    this.numWheels = 2;
  }
}

Map

  • Map is a new data structure in ES2015
  • Also called "hash maps" or "hash" in other languages
  • Similar to objects, except the keys can be ANY data type!
  • Data type for JavaScript objects is always a string for keys
  • CRUD of maps:
    • C: using the 'new' keyword
    • R: get(key)
    • U: set(key, value)
    • D: delete(key),
  • Useful Properties:
    • size()
  • Maps implement a Symbol.iterator which means we can use a for...of loop!
  • We can access everything (key & value) with .entries() and destructuring
  • Why Use Maps?
    • Finding the size is easy - no more loops or Object.keys(obj).length
    • 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 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
  • Cannot iterate over a WeakMap
    • Values in a WeakMap can be cleared from memory if there is no reference to them
  • More performant than maps
In [18]:
var firstMap = new Map;

firstMap.set(1, 'Elie');
firstMap.set(false, 'a boolean');
firstMap.set('nice', 'a string');
firstMap.delete('nice');
Out[18]:
true
In [20]:
firstMap.size;
Out[20]:
2

key can be of any type!

In [22]:
var arrayKey = [];
firstMap.set(arrayKey, [1,2,3,4,5]);
Out[22]:
Map {
  1 => 'Elie',
  false => 'a boolean',
  [] => [ 1, 2, 3, 4, 5 ],
  {} => { a: 1 },
  [] => [ 1, 2, 3, 4, 5 ] }
In [23]:
var objectKey = {};
firstMap.set(objectKey, {a:1});
Out[23]:
Map {
  1 => 'Elie',
  false => 'a boolean',
  [] => [ 1, 2, 3, 4, 5 ],
  {} => { a: 1 },
  [] => [ 1, 2, 3, 4, 5 ],
  {} => { a: 1 } }

Extracting Values

In [25]:
firstMap.get(1);
Out[25]:
'Elie'
In [26]:
firstMap.get(false);
Out[26]:
'a boolean'
In [27]:
firstMap.get(arrayKey);
Out[27]:
[ 1, 2, 3, 4, 5 ]
In [28]:
firstMap.get(objectKey);
Out[28]:
{ a: 1 }

Iterating over Map

In [30]:
firstMap.forEach(v => console.log(v));
Elie
a boolean
[ 1, 2, 3, 4, 5 ]
{ a: 1 }
[ 1, 2, 3, 4, 5 ]
{ a: 1 }
In [32]:
firstMap.values();
Out[32]:
MapIterator {
  'Elie',
  'a boolean',
  [ 1, 2, 3, 4, 5 ],
  { a: 1 },
  [ 1, 2, 3, 4, 5 ],
  { a: 1 } }
In [33]:
firstMap.keys();
Out[33]:
MapIterator { 1, false, [], {}, [], {} }

Accessing Keys and Values in a Map We can access everything with .entries() and destructuring

In [34]:
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'

Set

  • Set is a new data structure in ES2015
  • All values in a set are unique. Good way to remove duplicates.
    • When you add duplicate values in a set, it does not change
  • Any type of value can exist in a set
  • CRUD of Set:
    • C: using the new keyword
    • R: has()
    • U: add()
    • D: delete()
  • Useful Properties:
    • size()
  • Can iterate using for...of loop
  • When to use Sets?
    • When you do not need to identify values with keys or care about the ordering of values

Weak Set

  • 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
In [39]:
var s = new Set;
s
Out[39]:
Set {}

Can also be created from an array

In [40]:
var s2 = new Set([3,1,4,1,2,1,5]);
s2
Out[40]:
Set { 3, 1, 4, 2, 5 }
In [41]:
s.add(10);
s;
Out[41]:
Set { 10 }
In [42]:
s.add(20);
s;
Out[42]:
Set { 10, 20 }
In [44]:
s.add(10);
s;
Out[44]:
Set { 10, 20 }
In [45]:
s.size;
Out[45]:
2
In [46]:
s.has(10);
Out[46]:
true
In [47]:
s.delete(20);
Out[47]:
true
In [48]:
s.size;
Out[48]:
1
In [49]:
// s2[Symbol.iterator]; // function(){}...
// we can use a for...of loop!

Exercise: Map and Set

class MessageBoard {

In your constructor method, you should assign two properties for each object created from the MessageBoard class. The first should be a property called messages which is an empty Map, and the second is a property called id which has a value of 1.

var m = new MessageBoard

m.hasOwnProperty('messages') // true
m.messages.constructor // function Map() { [native code] }
m.hasOwnProperty('id') // true
m.id // 1
constructor(){
  this.messages = new Map;
  this.id = 1;
}

Add a method called addMessage which accepts a string. The function should add a key and value to the messages map with a key of whatever the value of this.id is and a value of whatever the string is that is passed to the function. The function should return the object created from the class so that the method can be chained. (HINT - to implement the last part, make sure to return this).

var m = new MessageBoard
m.addMessage('hello');
m.messages.size // 1
m.addMessage('awesome!') // m
m.addMessage('awesome!').addMessage('nice!').addMessage('cool!')

To chain methods, you have to return the object by returning this , that way you can chain the method because the returned object will have that method.

addMessage(str){
  this.messages.set(this.id, str);
  this.id++;
  return this; // tricky
}

Add a method called findMessageById which accepts a number and returns the message in the messages map with the same key as the number passed to the function. If the key is not found in the messages map, the function should return undefined.

var m = new MessageBoard
m.addMessage('hello!')
m.addMessage('hi!')
m.addMessage('whats up?')
m.findMessageById(1) // 'hello!'
m.findMessageById(2) // 'hi!'
m.findMessageById(3) // 'whats up?'
m.findMessageById(4) // undefined
m.findMessageById() // undefined
findMessageById(id){
  return this.messages.get(id);
}

Add a method called findMessageByValue which accepts a string and returns the message in the messages map with the same value as the string passed to the function. If the value is not found in the messages map, the function should return undefined.

var m = new MessageBoard
m.addMessage('hello!')
m.addMessage('hi!')
m.addMessage('whats up?')
m.findMessageByValue('hello!') // 'hello!'
m.findMessageByValue('hi!') // 'hi!'
m.findMessageByValue('whats up?') // 'whats up?'
m.findMessageByValue('nothing here') // undefined
m.findMessageByValue() // undefined
findMessageByValue(str){
    for (let msg of this.messages.values()){
    if (msg === str) {
      return str;
    }
    }
}

Add a method called removeMessage which accepts a number and removes a message in the messages map with a key of the number passed to the function.

var m = new MessageBoard
m.addMessage('hello!')
m.addMessage('hi!')
m.addMessage('whats up?')
m.removeMessage(1)
m.removeMessage(2)
m.messages.size // 1
m.removeMessage() // m
removeMessage(num){
  this.messages.delete(num);
  return this; //for method chaining
}

Add a method called numberOfMessages which returns the number of keys in the messages map

var m = new MessageBoard
m.addMessage('hello!')
m.addMessage('hi!')
m.addMessage('whats up?')
m.numberOfMessages() // 3
numberOfMessages(){
  return this.messages.size;
}

Add a method called messagesToArray which returns an array of all of the values in the messages map

var m = new MessageBoard
m.addMessage('hello!')
m.addMessage('hi!')
m.addMessage('whats up?')
m.messagesToArray() // ['hello!', 'hi!', 'whats up?'])
messagesToArray(){
    return Array.from(this.messages.values());
}

Alternative

messagesToArray(){
    return [...this.messages.values()];
}

Write a function called uniqueValues which accepts an array and returns the number of unique values in the array

uniqueValues([1,1,2,2,2,3,3,3,3,4,4,4,5,5,6]) // 6
In [51]:
function uniqueValues(arr){
  var s = new Set(arr);
  return s.size;
}

Write a function called hasDuplicates which accepts an array and returns true if there are duplicate values in the array, otherwise it should return false.

hasDuplicates([1,1,2,2,2,3,3,3,3,4,4,4,5,5,6]) // true
hasDuplicates([1,2,3,4,5,6]) // false
hasDuplicates([]) // false
In [52]:
function hasDuplicates(arr){
  let s = new Set(arr);
  if (arr.length === s.size){
    return false;
  }
  return true;
}

Tricky Write a function called countPairs which accepts an array of numbers and a number. The function should return the number of unique pairs (two numbers) that sum up to the number passed to the function.

countPairs([8,2,6,4,10,0],10) // 3
countPairs([8,2],10) // 1
countPairs([1,2],10) // 0
countPairs([1,2,3,4,5],10) // 0
countPairs([],10) // 0
countPairs([5,4,-10,6,-20,16],-4) // 2
countPairs([0,-4],-4) // 1
In [53]:
function countPairs(arr, num){
  var cache = new Set(arr);
  var count = 0;
  for(let val of arr){
    cache.delete(val); // prevent us from dealing with a pair of the same number
    if(cache.has(num - val)){ // check if item exist in remaining set to sum to 2nd parameter
      count++;
    }
  }
  return count;
}

Promises

  • Promise is a constructor function to help us manage asynchronous code
  • A one time guaranteed return of some future value (a place holder)
  • 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
  • Analogy:
    • 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
  • Difference between Promise and Callback:
    • You need to create a new promise if you want to perform the same asynchronous operation again
  • Promises from other frameworks/ libraries before ES2015
    • 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'
  • Create your 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
  • 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
In [54]:
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

In [55]:
displayAtRandomTime().then(function(value){ // .then: handle 'resolved'
    console.log(value);
}).catch(function(error){ // .catch: handle 'reject'
    console.log(error);
});
No!

Chaining 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!

The following codes use jQuery and only works on website using jQuery with the console You can check whether a website use jQuery by typing 'jQuery' in the console. If you get an error it is not loaded on that page, if you see a function it is

var years = [];
$.getJSON('https://omdbapi.com?t=titanic&apikey=thewdb')

.then(function(movie){
  years.push(movie.Year);
  return $.getJSON('https://omdbapi.com?t=shrek&apikey=thewdb');
})

.then(function(movie){
  years.push(movie.Year);
  console.log(years);
})

console.log('ALL DONE!');

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');

Using Promise.all

Promise.all([titanicPromise, shrekPromise, braveheartPromise]).then(function(movies){
  return movies.forEach(function(value){
    console.log(value.Year);
  });
});

// 1997
// 2001
// 1995

Exercise: Promises (Pending)

  1. Write a function called hasMostFollowers, which accepts a variable number of arguments. You should then make an AJAX call to the Github User API (https://developer.github.com/v3/users/#get-a-single-user) to get the name and number of followers of each argument. The function should return a string which displays the username who has the most followers.

Hint - Try to use Promise.all to solve this and remember that the jQuery AJAX methods ($.getJSON, $.ajax, etc.) return a promise.

hasMostFollowers('elie','tigarcia','colt').then(function(data){
 console.log(data)
});

// output: "Colt has the most followers with 424" */
  1. Write a function called starWarsString, which accepts a number. You should then make an AJAX call to the Star Wars API (https://swapi.co/ ) to search for a specific character by the number passed to the function. Your function should return a promise that when resolved will console.log the name of the character.
    starWarsString(1).then(function(data){
    console.log(data)
    })
    // output: "Luke Skywalker" */
    

Bonus 1 - Using the data from the previous AJAX call above, make another AJAX request to get the first film that character is featured in and return a promise that when resolved will console.log the name of the character and the film they are featured in

starWarsString(1).then(function(data){
  console.log(data)
})

// Output: "Luke Skywalker is featured in The Empire Strikes Back, directed by Irvin Kershner" */

Bonus 2 - Using the data from Bonus 1 - make another AJAX call to get the information about the first planet that the film contains. Your function should return a promise that when resolved will console.log the name of the character and the film they are featured in and the name of the planet.

starWarsString(1).then(function(data){
  console.log(data)
})

// Output: "Luke Skywalker is featured in The Empire Strikes Back, directed by Irvin Kershner and it takes place on Hoth" */

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
  • We can place multiple yield keywords inside of a generator function to pause multiple times! (example below)
  • Since generators implement a Symbol.iterator property we can use a for...of loop! (example below)
  • We can use generators to pause asynchronous code
In [59]:
function* pauseAndReturnValues(num){
  for(let i = 0; i < num; i++){
  	yield i;
  }
}

var gen = pauseAndReturnValues(5);

gen.next();
Out[59]:
{ value: 0, done: false }
In [60]:
gen.next();
Out[60]:
{ value: 1, done: false }
In [61]:
gen.next();
Out[61]:
{ value: 2, done: false }
In [62]:
gen.next();
Out[62]:
{ value: 3, done: false }
In [63]:
gen.next();
Out[63]:
{ value: 4, done: false }
In [64]:
gen.next();
Out[64]:
{ value: undefined, done: true }

Yielding Multiple Values

In [65]:
function* printValues(){
  yield "First";
  yield "Second";
  yield "Third";
}

var g = printValues();
g.next().value;
Out[65]:
'First'
In [66]:
g.next().value;
Out[66]:
'Second'
In [67]:
g.next().value;
Out[67]:
'Third'

Iterating over a Generator

In [68]:
function* pauseAndReturnValues(num){
  for(let i = 0; i < num; i++){
    yield i;
  }
}

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

Async Generator

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

The next value returned is a promise so let's resolve it

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

Other ES2015 Methods

Object.assign()

  • Create copies of objects without the same reference!
  • Notice the first parameter i.e. {}. Gotcha #1: If omitted, still object by reference
  • Not a deep clone (shallow copy). Gotcha #2: If we have objects inside of the object we are copying - those still have a reference! (example below)
    • If you need a deep clone, you can write your own function or use other data structures or use popular library
  • Object.assign vs Object.create
    • Object.assign gives you more flexibility to add other keys and values to the new object you make

ES5

In [69]:
var o = {name: "Elie"};
var o2 = o;

Same reference - change properties of o2 affects o!

In [70]:
o2.name = "Tim";
Out[70]:
'Tim'
In [71]:
o.name;
Out[71]:
'Tim'

ES2015

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

o2.name = "Tim";
o.name;
Out[72]:
'Elie'

Not a Deep Clone!

ES2015

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

o2.instructors.push("Colt");
o.instructors;
Out[73]:
[ 'Elie', 'Tim', 'Colt' ]

Array.from()

  • Convert other data types into arrays

ES5

var divs = document.getElementsByTagName("div"); // returns an array-like-object
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);

ES2015

In [74]:
var firstSet = new Set([1,2,3,4,3,2,1]);
firstSet;
Out[74]:
Set { 1, 2, 3, 4 }
In [77]:
var arrayFromSet = Array.from(firstSet);
arrayFromSet;
Out[77]:
[ 1, 2, 3, 4 ]

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
  • Find value without using a for loop
In [78]:
var instructors = [{name: "Elie"}, {name: "Matt"}, {name: "Tim"}, {name: "Colt"}];

instructors.find(function(val){
  return val.name === "Tim";
});
Out[78]:
{ name: 'Tim' }

findIndex()

Similar to find, but returns an index or -1 if the value is not found

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

instructors.findIndex(function(val){
  return val.name === "Tim";
});
Out[79]:
2

Includes()

  • returns a boolean if a value is in a string - easier than using indexOf

ES5

In [80]:
"awesome".indexOf("some") > -1;
Out[80]:
true

ES2015

In [81]:
"awesome".includes("some");
Out[81]:
true

Number.isFinite()

  • A handy way for handling NaN being a typeof number

ES5

In [82]:
function seeIfNumber(val){
  if(typeof val === "number" && !isNaN(val)){
    return "It is a number!";
  }
}

ES2015

In [83]:
function seeIfNumber(val){
  if(Number.isFinite(val)){
    return "It is a number!";
  }
}

Exercise: ES2015 Methods

Write a function called copyObject, which accepts one parameter, an object. The function should return a shallow copy of the object.

var o = {name: 'Elie'}
var o2 = copyObject({}, o)
o2.name = "Tim"
o2.name // 'Tim'
o.name // 'Elie'
In [84]:
function copyObject(obj){
  return Object.assign({}, obj);
}

Write a function called checkIfFinite which accepts one parameter and returns true if that parameter is a finite number.

checkIfFinite(4) // true
checkIfFinite(-3) // true
checkIfFinite(4. // .toEqual(true
checkIfFinite(NaN) // false
checkIfFinite(Infinity) // false
In [85]:
function checkIfFinite(num){
  return Number.isFinite(num);
}

Write a function called areAllNumbersFinite which accepts an array and returns true if every single value in the array is a finite number, otherwise return false.

var finiteNums = [4,-3,2.2]
var finiteNumsExceptOne = [4,-3,2.2,NaN]
areAllNumbersFinite(finiteNums) // true
areAllNumbersFinite(finiteNumsExceptOne) // false
In [86]:
function areAllNumbersFinite(arr){
  return arr.every(Number.isFinite);
}

Write a function called convertArrayLikeObject which accepts a single parameter, an array like object. The function should return the array like object converted to an array.

var divs = document.getElementsByTagName('div')
divs.reduce // undefined

var converted = convertArrayLikeObject(divs)
converted.reduce // funciton(){}...
In [87]:
function convertArrayLikeObject(obj){
  return  Array.from(obj);
}

Write a function called displayEvenArguments which accepts a variable number of arguments and returns a new array with all of the arguments that are even numbers.

displayEvenArguments(1,2,3,4,5,6) // [2,4,6]
displayEvenArguments(7,8,9) // [8]
displayEvenArguments(1,3,7) // []
In [88]:
function displayEvenArguments(){
  return Array.from(arguments).filter(val => val %2 === 0);
}

Alternative

In [89]:
function displayEvenArguments(...args){
  return args.filter(val => val %2 === 0);
}