Skip to content

Latest commit

 

History

History
2524 lines (2048 loc) · 65.9 KB

WesBos_es6-for-everyone.md

File metadata and controls

2524 lines (2048 loc) · 65.9 KB

Notes on ES6 for Everyone

Notes from the Wes Bos course at ES6.io

ES6 for Everyone

Personal highlights:

  • Function scope vs block scope
  • Block scope as replacement of IIFE
  • Arrow functions don't rebind 'this'
  • Parenthesis needed around object literals for implicit return in arrow functions
  • Top-level functions on event handlers typically use non-arrow functions because rebinding 'this' is desired *
    • Inside event handlers though, arrow functions are great because 'this' can usefully refer to the event triggering element
  • Default function arguments (9)
  • Ternary with conditional logic for template literal (13)
  • Render function within a template literal (13)
  • Tagged template literals (14)
  • Assembling a tagged template literal programmatically with reduce (15 + 16)
  • String methods: startsWith, endsWith, includes (17)
  • Destructuring, renaming while destructuring, default values (18)
  • Rest operator (19)
  • Swapping variables through destructuring (20)
  • Returning multiple values through destructuring (21) *
  • Named defaults from an empty array (21)
  • For-of loop, break, continue, entries (22)
  • Second parameter of Array.from() runs map() (25)
  • Array.find() + Array.findIndex() (26)
  • Spread operators as function arguments (31)
  • Rest operators as function parameters (32)
  • Updated key syntax on object literals (33)
  • Symbols (38)
  • ESLint + Git Hooks (44)
  • Default exports vs named exports in JS modules (45)
  • Babel "env" setting in .babelrc (49)
  • Static methods on classes (52)
  • Proxies, handlers, traps (58)
  • Sets + WeakSets (61)
  • Garbage collection (63)
  • Maps used for metadata collection on an object (64)
  • Promises + async/await (69)
  • Async/await error handling with higher-order functions (70) *
  • Class properties (73)
  • Array.includes() (75)
  • Trailing commas (76)
  • Object.entries() + Object.values() (77)
  • Curly brackets have many meanings
    • object literals
    • destructuring variables out of an object
    • importing a named export

Module 1 - New Variables — Creation, Updating and Scoping

01 - var-let-const

var

  • Function scope: Available inside function where a var is created (local variable)
  • Or globally scoped if not in a function
    • Global scoped is generally not what we want. To update a variable outside a function we should return from the function
    • if{} blocks are not functions and will leak var variable scope. This isn't intuitive and exemplifies one of the big upgrades we get with block scope
var age = 100;
if(age > 12) {
  var dogYears = age * 7;
  console.log(`You are ${dogYears} dog years old!`);
}

console.log(dogYears) //700

let

  • Block scope: Available inside block where a let is created
  • if{} blocks will constrain scope
var age = 100;
if(age > 12) {
  let dogYears = age * 7;
  console.log(`You are ${dogYears} dog years old!`);
}

console.log(dogYears) //undefined

02 - let VS const

In contrast to var, let and const cannot be redeclared in the same scope.

let points = 50;
let winner = false;
let points = 40; // error

Object.freeze()

The Object.freeze() method freezes an object: that is, prevents new properties from being added to it; prevents existing properties from being removed; and prevents existing properties, or their enumerability, configurability, or writability, from being changed, it also prevents the prototype from being changed.

03 - let and const in the Real World

IIFE

  • Thanks to block scope, blocks + let/const constrain scope just like an IIFE
{
  const name = 'wes';
}

...can replace...

(function(){
  const name = 'wes';
})()

for loop

  • The block scope of let prevents the variable in a for loop from escaping into global scope.
  • Also creates nine scopes in the example below that all retain their declared value, in contrast to var which would be overridden and simply output the final value 10 times
for(let i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log('The number is ' + i);
  },1000);
}

04 - Temporal Dead Zone

  • var can be accessed prior to being declared, although it's value is undefined
console.log(pizza); //undefined because in the TEMPORAL DEAD ZONE
var pizza = 'Deep Dish 🍕🍕🍕';
  • let/const cannot be accessed before being declared
console.log(pizza); //Error
const pizza = 'Deep Dish 🍕🍕🍕';

05 - Is var Dead? What should I use?

  • Most familiar to me:
  • use const by default
  • only use let if rebinding is needed
  • (var shouldn’t be used in ES2015)

https://mathiasbynens.be/notes/es6-const

Module 2 - Function Improvements: Arrows and Default Arguments

06 - Arrow Functions Introduction

Some benefits of arrow functions

  • Concise
  • Implicit returns
const fullNames4 = names.map(name => `${name} bos`);

vs

const fullNames3 = names.map(name => {
  return `${name} bos`;
});
  • Doesn't rebind the value of this

  • Arrow functions are always anonymous functions, but they can be declared in a variable

const sayMyName = (name) => {
  alert(`Hello ${name}!`)
}

One benefit of a named function is that the name can be helpful in tracking errors in a stack trace

07 - More Arrow Function Examples

  • Implicit return with object literal
    • Removing function block braces works for other types of returned values, but gets weird with an object literal, which has its own braces

Solution: parenthesis around object literal:

const win = winners.map((winner, i) => ({name: winner, race, place: i + 1}));

Nice example of implicit return of a boolean

const ages = [23,62,45,234,2,62,234,62,34];
const old = ages.filter(age => age >= 60);

08 - Arrow Functions and 'this'

  • When using an arrow function, the value of this is not rebound, so can end up with parent scope on a click event handler.
const box = document.querySelector('.box');
box.addEventListener('click', () => console.log(this)) //Window NOT box!
  • Generally good then to have a non-arrow function at the top-level of an event handler because we want this to establish a specific context relative to the element associated with triggering the event.
const box = document.querySelector('.box');
box.addEventListener('click', function() {console.log(this);}) // Box!
  • Arrow function inherits value of this from parent function, which is useful inside a callback.
const box = document.querySelector('.box');
box.addEventListener('click', function() {

  this.classList.toggle('opening');
  setTimeout(() => {
    console.log(this); // Still box, but would be Window if non-arrow function! bc new context unbound to box would be created by non-arrow function
    this.classList.toggle('open');
  }, 500);
});

Inheriting this via arrow functions allows you to achieve the same thing as

var that = this;

09 - Default Function Arguments

function calculateBill(total, tax = 0.13, tip = 0.15) {
  return total + (total * tax) + (total * tip);
}

const totalBill = calculateBill(100, undefined, 0.25);
  • undefined is still needed as a parameter in the function invocation, which seems odd to me

Can replace:

tax = tax || 0.13;

10 - When NOT to Use an Arrow Function

(1) Top level of an event handler callback where 'this' is needed

const button = document.querySelector('#pushy');
button.addEventListener('click', function() {
  console.log(this); //button
  this.classList.toggle('on');
});

const button = document.querySelector('#pushy');
button.addEventListener('click', () => {
  console.log(this); //Oh no, Window!
  this.classList.toggle('on');
});

(2) When you need a method to bind to an object

const person = {
  points: 23,
  score() {
    console.log(this); // person {...}
    this.points++; // Increments object attribute
  }
  // ^  SAME AS  ^
  // score: function() {}
}

const person = {
  points: 23,
  score: () => {
    console.log(this); // Oh no, Window!
    this.points++; // Undefined
  }
}

(3) When you need to add a prototype method

class Car {
  constructor(make, colour) {
    this.make = make;
    this.colour = colour;
  }
}

const beemer = new Car('bmw', 'blue');
const subie = new Car('Subaru', 'white');

Car.prototype.summarize = function() {
   return `This car is a ${this.make} in the colour ${this.colour}`;
};

beemer.summarize() //"This car is a bmw in the colour blue"

class Car {
  constructor(make, colour) {
    this.make = make;
    this.colour = colour;
  }
}

const beemer = new Car('bmw', 'blue');
const subie = new Car('Subaru', 'white');

Car.prototype.summarize = () => {
   return `This car is a ${this.make} in the colour ${this.colour}`;
};

beemer.summarize() // "This car is a undefined in the colour undefined"

(4) When you need arguments object

const orderChildren = function() {
  // orderChildren('beyonce', 'solange')
  console.log(arguments); // ["beyonce", "solange"]
  const children = Array.from(arguments);
  return children.map((child, i) => {
    return `${child} was child #${i + 1}`;
  })
}

const orderChildren = () => {
  // orderChildren('beyonce', 'solange')
  console.log(arguments); // Uncaught ReferenceError: arguments is not defined
  const children = Array.from(arguments);
  return children.map((child, i) => {
    return `${child} was child #${i + 1}`;
  })
}

11 - Arrow Functions Exercises

My answer:

const videoSeconds = videoTimes.map(videoTime => {
  const [minutes , seconds] = videoTime
    .split(':')
    .map(parseFloat);

  return minutes * 60 + seconds;
});
  • Declaration of minutes and seconds is happening via destructuring since the other side of the equals operator will have an array.

Bos answer:

.map(timecode => {
  const parts = timecode.split(':').map(part => parseFloat(part));
  return (parts[0] * 60) + parts[1];
})

Module 3 - Template Strings

13 - Creating HTML fragments with Template Literals

const dogs = [
  { name: 'Snickers', age: 2 },
  { name: 'Hugo', age: 8 },
  { name: 'Sunny', age: 1 }
];

const markup = `
  <ul class="dogs">
    ${dogs.map(dog => `
      <li>
        ${dog.name}
        is
        ${dog.age * 7}
      </li>`).join('')}
  </ul>
`;
  • Possible to iterate and generate template literals inside another template literal.
const song = {
  name: 'Dying to live',
  artist: 'Tupac',
  featuring: 'Biggie Smalls'
};

const markup = `
  <div class="song">
    <p>
      ${song.name}${song.artist}
      ${song.featuring ? `(Featuring ${song.featuring})` : ''}
    </p>
  </div>
`;
  • Ternary within template literal to handle string that may or may not be present
const beer = {
  name: 'Belgian Wit',
  brewery: 'Steam Whistle Brewery',
  keywords: ['pale', 'cloudy', 'spiced', 'crisp']
};

function renderKeywords(keywords) {
  return `
    <ul>
      ${keywords.map(keyword => `<li>${keyword}</li>`).join('')}
    </ul>
  `;
}

const markup = `
  <div class="beer">
    <h2>${beer.name}</h2>
    <p class="brewery">${beer.brewery}</p>
    ${renderKeywords(beer.keywords)}
  </div>
`;
${renderKeywords(beer.keywords)}
  • Possible to delegate to a render function the work of generating a template literal 😍
  • Parallels with React patterns where a component is designated to handle a specific bit of markup generation

14 - Tagged Template Literals

function highlight(strings, ...values) {
  debugger;
}
  • Dev tools > sources > scope > local
    • Useful way to assess a function's local scope
function highlight(strings, ...values) {
  let str = '';
  strings.forEach((string, i) => {
    str += `${string} <span contenteditable class="hl">${values[i] || ''}</span>`;
  });
  return str;
}

const name = 'Snickers';
const age = 100;

const sentence = highlight`My dog's name is ${name} and he is ${age} years old`;
  • Pass in the string and the tagged template literal is broken into its pieces, which can then be manipulated as they are reassembled
    • rest spread useful in accepting as many arguments as are passed in
    • Suppose this is how you could make something like "drunk-ify text"

15 - Tagged Templates Exercise

const dict = {
  HTML: 'Hyper Text Markup Language',
  CSS: 'Cascading Style Sheets',
  JS: 'JavaScript'
};

function addAbbreviations(strings, ...values) {
  const abbreviated = values.map(value => {
    if(dict[value]) {
      return `<abbr title="${dict[value]}">${value}</abbr>`;
    }
    return value;
  });

  return strings.reduce((sentence, string, i) => {
    return `${sentence}${string}${abbreviated[i] || ''}`;
  }, '');
}

const first = 'Wes';
const last = 'Bos';
const sentence = addAbbreviations`Hello my name is ${first} ${last} and I love to code ${'HTML'}, ${'CSS'} and ${'JS'}`;
const abbreviated = values.map(value => {
  if(dict[value]) {
    return `<abbr title="${dict[value]}">${value}</abbr>`;
  }
  return value;
});
  • Mapping over all values in template literal and testing if a given value can serve as key in the dict object
    • If not, just returning the value
return strings.reduce((sentence, string, i) => {
  return `${sentence}${string}${abbreviated[i] || ''}`;
}, '');
  • Assembling a string programmatically from several sources using reduce

16 - Sanitizing User Data with Tagged Templates

  • XXS = Cross-site Scripting
    • Security risk in using innerHTML w/o sanitizing as evil JS could get run—as in onload below
  function sanitize(strings, ...values) {
    const dirty = strings.reduce((prev, next, i) => `${prev}${next}${values[i] || ''}`, '');
    return DOMPurify.sanitize(dirty);
  }

  const first = 'Wes';
  const aboutMe = `I love to do evil <img src="http://unsplash.it/100/100?random" onload="alert('you got hacked');" />`;

  const html = sanitize`
    <h3>${first}</h3>
    <p>${aboutMe}</p>
  `;

  const bio = document.querySelector('.bio');
  bio.innerHTML = html;
const dirty = strings.reduce((prev, next, i) => `${prev}${next}${values[i] || ''}`, '');
  • Seems the minimal way to reconstruct from a tagged template using reduce
return DOMPurify.sanitize(dirty);
  • DOMPurify is imported

Module 4 - Additional String Improvements

17 - New String Methods

Four new methods

  • Largely conveniences to avoid reliance on regex
  • Case sensitivity is a bit of a drawback compared to regex
const course = 'RFB2';
const flightNumber = '20-AC2018-jz';
const accountNumber = '825242631RT0001';

.startsWith()

course.startsWith('RF'); //true
course.startsWith('rf'); //false
  • Cannot make case insensitive

.endsWith()

flightNumber.endsWith('jz'); //true
accountNumber.endsWith('RT'); //false
accountNumber.endsWith('RT', 11); //true
  • Second parameter determines how much of the original string to consider
    • Here the first 11 characters

.includes()

flightNumber.includes('AC'); //true
flightNumber.includes('ac'); //false
  • Backstory on contains() + MooTools name space

.repeat()

function leftPad(str, length = 20) {
  return `→ ${' '.repeat(length - str.length)}${str}`;
}
  • Repeats a given string as many times as specified in the parameter
  • In this case, repeat is used to right align text by inserting spaces programatically

Module 5 - Destructuring

  • Key syntax point is to match the left and right sides:
const { A, B } = { A: 'value1', B: 'value2' };
const [ A, B ] = ['value1', 'value2'];

In both cases:

console.log(A); //"value1"
console.log(typeof(A)); //string

18 - Destructuring Objects

Object destructuring syntax

const person = {
  first: 'Wes',
  last: 'Bos',
  country: 'Canada',
  city: 'Hamilton',
  twitter: '@wesbos'
};

const { first, last, twitter } = person;
const { } = person;
  • The brackets here are destructuring syntax, not a block or an object.
    • When destructuring an object, we use curly brackets. But when destructuring an array, we'll use square brackets.
    • A bit like a type hint
  • Top-level variable

Destructuring deeply nested data

const wes = {
  first: 'Wes',
  last: 'Bos',
  links: {
    social: {
      twitter: 'https://twitter.com/wesbos',
      facebook: 'https://facebook.com/wesbos.developer',
    },
    web: {
      blog: 'https://wesbos.com'
    }
  }
};

const { twitter, facebook } = wes.links.social;
  • With nested data

Renaming

const { twitter: tweet, facebook: fb } = wes.links.social;
  • Can rename as you destructure
  • Otherwise, we're a bit "trapped" because "twitter" is the key we need to use
    • Here the variable names are tweet and fb

Setting defaults

const settings = { width: 300, color: 'black' }
const { width = 100, height = 100, color = 'blue', fontSize = 25} = settings;
  • If the value exists in settings then it will override the default

Combined example

// Object destructuring with variable renaming & default values
const { w: width = 400, h: height = 500 } = { w: 800 }
  • Not necessarily a common use case, but models all concepts

19 - Destructuring Arrays

Array destructuring syntax

const details = ['Wes Bos', 123, 'wesbos.com'];
const [name, id, website] = details;
  • Uses square brackets, as opposed to curly brackets for objects
const data = 'Basketball,Sports,90210,23,wes,bos,cool';
const [itemName, category, sku, inventory] = data.split(',');
  • Creating an array from a string with split(), and then immediately destructuring into the four desired variables
const team = ['Wes', 'Harry', 'Sarah', 'Keegan', 'Riker'];
const [captain, assistant, ...players] = team;
console.log(players) // ['Sarah', 'Keegan', 'Riker'] ;
  • Rest operator allows us to get all the remaining items

20 - Swapping Variables with Destructuring

let inRing = 'Hulk Hogan';
let onSide = 'The Rock';

console.log(inRing, onSide); // 'Hulk Hogan', 'The Rock'

[inRing, onSide] = [onSide, inRing];

console.log(inRing, onSide); // 'The Rock', 'Hulk Hogan'
[inRing, onSide] = [onSide, inRing];
  • On the right, creating an array which is immediately destructured into the two assignments on the left

21 - Destructuring Functions - Multiple returns and named defaults

Multiple returns

function convertCurrency(amount) {
  const converted = {
    USD: amount * 0.76,
    GPB: amount * 0.53,
    AUD: amount * 1.01,
    MEX: amount * 13.30
  };
  return converted;
}

const { USD, GBP } = convertCurrency(100);
  • While we're technically still just returning the single object, this effectively allows multiple values from a single function return

Named defaults

function tipCalc({ total = 100, tip = 0.15, tax = 0.13 } = {}) {
  return total + (tip * total) + (tax * total);
}

const bill = tipCalc({ tip: 0.20, total: 200 });
  • Versatile syntax allowed by passing an object into a function
    • Order of values passed in is not important
    • Don't need to pass in undefined for a parameter value like we did in video 5
    • Empty object is a bit unintuitive as a sort of second-tier default, but needed in case no values are passed in as parameters

Module 6 - Iterables and Looping

Built-in JS iterables

String, Array, TypedArray, Map and Set are all built-in iterables, because their prototype objects all have a Symbol.iterator method.

22 - The for of loop

for-of loop

  • Can be used with any kind of data except objects
  • Allows break and continue
const cuts = ['Chuck', 'Brisket', 'Shank', 'Short Rib'];

for (const cut of cuts) {
  if(cut === 'Brisket') {
    continue;
  }
  console.log(cut);
}
  • Existing methods of iterating and their drawbacks
    • for loop
      • Scope issue with var, slightly chunky syntax
    • forEach
      • No way to abort a loop with something like break
      • No way to skip over with continue
    • for-in loop
      • Weirdly, will also iterate over anything added to the prototype, not only the items in the iterable collection
      • 3rd party code might extend prototype (MooTools)

23 - The for-of Loop in Action

for (const [i, cut] of cuts.entries()) {
  console.log(`${cut} is the ${i + 1} item`);
}
  • entries is immediately destructured into two variables

Array.prototype.entries(): The entries() method returns a new Array Iterator object that contains the key/value pairs for each index in the array.

function addUpNumbers() {
  let total = 0;
  for (const num of arguments) {
    total += num;
  }
  return total;
}

addUpNumbers(10,23,52,34,12,13,123);
  • for-of loop with arguments allows us to accept any number of parameters
    • Best practice would be to convert arguments to an array, but if just iterating over then not a problem to forgo conversion
const ps = document.querySelectorAll('p');
for (const paragraph of ps) {
  paragraph.addEventListener('click', function() {
    console.log(this.textContent);
  });
}
  • I'd typically use forEach to add event listeners, but for-of might be useful if you wanted to use break or continue to more selectively apply event handlers

24 - Using for-of with Objects

  • Object.entries() is standard in ES2017
    • Subject of video 77
const apple = {
  color: 'Red',
  size: 'Medium',
  weight: 50,
  sugar: 10
};

for (const prop in apple) {
  const value = apple[prop];
}
  • Short of Object.entries() Bos suggests just sticking with a for-in loop

Module 7 - An Array of Array Improvements

25 - Array.from() and Array.of()

  • Neither are on the prototype, but on Array itself

Arrary.from()

<div class="people">
  <p>Wes</p>
  <p>Kait</p>
  <p>Snickers</p>
</div>
const people = document.querySelectorAll('.people p');
const peopleArray = Array.from(people, person => person.textContent);
console.log(peopleArray); //["Wes", "Kait", "Snickers"]
  • Second parameter runs map method 🆒
function sumAll() {
  const nums = Array.from(arguments);
  return nums.reduce((prev, next) => prev + next, 0);
}

sumAll(2, 34, 23, 234, 234, 234234, 234234, 2342);
  • Common use case is to convert arguments to an array in order to use all the array methods, such as reduce above
    • Note that sumAll needs to be a non-arrow function since it relies on accessing arguments

Arrary.of()

const ages = Array.of(12,4,23,62,34);
console.log(ages); // [12, 4, 23, 62, 34]
  • Pretty straightforward

26 - Array.find() and Array.findIndex()

const posts = [
  {
     "code":"VBgtGQcSf",
     "caption":"Trying the new Hamilton Brewery beer. Big fan.",
     "likes":27,
     "id":"1122810327591928991",
     "display_src":"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/e35/12224456_175248682823294_1558707223_n.jpg"
  },
  {
     "code":"FpTyHQcau",
     "caption":"I'm in Austin for a conference and doing some training. Enjoying some local brew with my baby.",
     "likes":82,
     "id":"1118481761857291950",
     "display_src":"https://scontent.cdninstagram.com/hphotos-xpt1/t51.2885-15/e35/11326072_550275398458202_1726754023_n.jpg"
  }
];
const code = 'VBgtGQcSf';
const post = posts.find(post => post.code === code);
console.log(post); // { ENTIRE OBJECT THAT MATCHES THE CONDITION }
  • find() returns once a condition is true
    • If you wanted multiple results, then use filter()
  const postIndex = posts.findIndex(post => post.code === code);
  console.log(postIndex); // 22
  • findIndex(): Useful if you want to do something like delete a post or apply a highlight class

27 - Array .some() and .every()

  • Not a part of ES6, but included because useful and somewhat rare "in the wild"
const ages = [32, 15, 19, 12];

// 👵👨 is there at least one adult in the group?
const adultPresent = ages.some(age => age >= 18);
console.log(adultPresent); / /true

// 🍻 is everyone old enough to drink?
const allOldEnough = ages.every(age => age >= 19);
console.log(allOldEnough); // false

Module 8 - Say Hello to ...Spread and ...Rest

28 - Spread Operator Introduction

console.log([...'wes']); //["w", "e", "s"]
const featured = ['Deep Dish', 'Pepperoni', 'Hawaiian'];
const specialty = ['Meatzza', 'Spicy Mama', 'Margherita'];

console.log([...featured, ...specialty]);

const pizzas = [...featured, 'veg', ...specialty];
const fridayPizzas = [...pizzas];
  • Nice alternative to .concat()
  • Spread operator also allows an array to be copied instead of referenced
const fridayPizzas = pizzas;
fridayPizzas[0] = 'Yum-1';
console.log(pizzas[0]) // 'Yum-1'  - OH NO;
  • Referencing an array is just bad.

29 - Spread Exercise

  <h2 class="jump">SPREADS!</h2>

Wes:

  const heading = document.querySelector('.jump');
  heading.innerHTML = sparanWrap(heading.textContent);

  function sparanWrap(word) {
    return [...word].map(letter => `<span>${letter}</span>`).join('');
  }

Me:

const target = document.querySelector('.jump');
target.innerHTML = [...target.textContent].map(letter => `<span>${letter}</span>`).join('')
  • Separating the logic out into its own function makes Wes's solution more reusable and easier to reason about

30 - More Spread Examples

  const people = Array.from(document.querySelectorAll('.people p'));
  const people = [...document.querySelectorAll('.people p')];
  • Fair point that Array.from() reads more clearly to others
const comments = [
  { id: 209384, text: 'I love your dog!' },
  { id: 523423, text: 'Cuuute! 🐐' },
  { id: 632429, text: 'You are so dumb' },
  { id: 192834, text: 'Nice work on this wes!' },
];
const id = 632429;
const commentIndex = comments.findIndex(comment => comment.id === id);
const newComments = [...comments.slice(0,commentIndex), ...comments.slice(commentIndex + 1)];

31 - Spreading into a Function

Spread operators can be useful as arguments passed into functions

const inventors = ['Einstein', 'Newton', 'Galileo'];
const newInventors = ['Musk', 'Jobs'];
inventors.push(...newInventors);
const name = ['Wes', 'Bos'];

function sayHi(first, last) {
  alert(`Hey there ${first} ${last}`);
}

sayHi(...name);
  • Simply saying, pass in all elements of the name array

32 - The ...rest param in Functions and destructuring

Looks the exact same as spread operator but behaves differently as a function parameter

function convertCurrency(rate, ...amounts) {
  return amounts.map(amount => amount * rate);
}

const amounts = convertCurrency(1.54, 10, 23, 52, 1, 56);
const runner = ['Wes Bos', 123, 5.5, 5, 3, 6, 35];
const [name, id, ...runs] = runner;
console.log(name, id, runs); // Wes Bos 123 [5.5, 5, 3, 6, 35]

const team = ['Wes', 'Kait', 'Lux', 'Sheena', 'Kelly'];
const [captain, assistant, ...players] = team;
console.log(captain, assistant, players) // Wes Kait ["Lux", "Sheena", "Kelly"]

Module 9 - Object Literal Upgrades

33 - Object Literal Upgrades

If key has same name as variable can omit colon:

const first = 'snickers';
const last = 'bos';
const age = 2;
const breed = 'King Charles Cav';
const dog = {
  firstName: first,
  last,
  age,
  breed,
  pals: ['Hugo', 'Sunny']
};

Simple modal syntax removes need for colon and function keyword:

const modal = {
  create(selector) {

  },
  open(content) {

  },
  close(goodbye) {

  }
}
  • Can pass in parameter:

Previously would need to use:

const modal = {
  create: function(selector) {

  }

Can dynamically set keys on an object:

const key = 'pocketColor';
const value = '#ffc600';

const tShirt = {
  [key]: value,
  [`${key}Opposite`]: invertColor(value)
};

Computed property keys are possible in ES6:

const keys = ['size', 'color', 'weight'];
const values = ['medium', 'red', 100];

const shirt = {
  [keys.shift()]: values.shift(),
  [keys.shift()]: values.shift(),
  [keys.shift()]: values.shift(),
}

Module 10 - Promises

34 - Promises

const postsPromise = fetch('http://wesbos.com/wp-json/wp/v2/posts');

postsPromise
  .then(data => data.json())
  .then(data => { console.log(data) })
  .catch((err) => {
    console.error(err);
  })
  • fetch() returns a promise
  • At time of declaration postPromise is like an IOU
  • Think of then() like a callback that allows for synchronous execution (once the IOU returns)
  • .json() method is key to translating returned data into useful JSON

35 - Building your own Promises

Can think of a promise as: "I don't want to stop JS from running. I just want to start this thing, and once it comes back pick it up"

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(Error('Err wes isn\'t cool'));
  }, 1000);
});

myPromise
  .then(data => {
    console.log(data);
  })
  .catch(err => {
    console.error(err);
  });
  • resolve() + reject() methods come with the promise
  • Using an Error object in reject() allows the stack trace to locate the line where the error was thrown

36 - Chaining Promises + Flow Control

const posts = [
  { title: 'I love JavaScript', author: 'Wes Bos', id: 1 },
  { title: 'CSS!', author: 'Chris Coyier', id: 2 },
  { title: 'Dev tools tricks', author: 'Addy Osmani', id: 3 },
];

const authors = [
  { name: 'Wes Bos', twitter: '@wesbos', bio: 'Canadian Developer' },
  { name: 'Chris Coyier', twitter: '@chriscoyier', bio: 'CSS Tricks and CodePen' },
  { name: 'Addy Osmani', twitter: '@addyosmani', bio: 'Googler' },
];

function getPostById(id) {
  return new Promise((resolve, reject) => {
  // using a settimeout to mimick a databse
    setTimeout(() => {
      const post = posts.find(post => post.id === id);
      (post)
      ? resolve(post)
      : reject(Error('No Post Was Found!'));
    }, 1000)
  });
}

function hydrateAuthor(post) {
  return new Promise((resolve, reject) => {
    const authorObject = authors.find(author => author.name === post.author);
    if(authorObject) {
    // "hydrate" the post object with the author object
      post.author = authorObject;
      resolve(post)
    } else {
      reject(Error('Can not find the author'));
    }
  });
}

getPostById(1)
  .then(post => {
    return hydrateAuthor(post);
  })
  .then(post => {
    console.log(post);
  })
  .catch(err => {
    console.error(err);
  });
  • debugger -> sources -> scope
    • Kind of like console.log but with everything in the given scope
  • I suppose flow control means using promises to synchronously execute functions?
  • I'm using a ternary above, but Bos uses a plain if-else, which might make more sense since the second promise will use if-else

37 - Working with Multiple Promises

const weather = new Promise((resolve) => {
  setTimeout(() => {
    resolve({ temp: 29, conditions: 'Sunny with Clouds'});
  }, 2000);
});

const tweets = new Promise((resolve) => {
  setTimeout(() => {
    resolve(['I like cake', 'BBQ is good too!']);
  }, 500);
});

Promise
  .all([weather, tweets])
  .then(responses => {
    const [weatherInfo, tweetInfo] = responses;
    console.log(weatherInfo, tweetInfo)
  });
  • Promise.all() accepts an array of promises and waits for them all to resolve
const [weatherInfo, tweetInfo] = responses;
  • Great use case for destructuring as a means to declare const variables
const postsPromise = fetch('http://wesbos.com/wp-json/wp/v2/posts');
const streetCarsPromise = fetch('http://data.ratp.fr/api/datasets/1.0/search/?q=paris');

Promise
  .all([postsPromise, streetCarsPromise])
  .then(responses => {
    return Promise.all(responses.map(res => res.json()))
  })
  .then(responses => {
    console.log(responses);
  });
  • With Promise.all(), we still need to convert the response to JSON via a second Promise.all() invocation
  • Might consider global install of Browsersync to get beyond the CORS limitation fetch() to an actual API often will present

Module 11 - Symbols

38 - All About Symbols

  • 7th primitive type and a new primitive in JS

  • Previous 6: number, string, object, boolean, null, undefined

  • Can help avoid naming conflicts, but not really a highly demanded addition

  • Could be useful if you want to create a property in a unique way

    const wes = Symbol('Wes');
    const person = Symbol('Wes');
    console.log(wes === person) // false;
  • Pass in a descriptor (here 'Wes')

  • Symbol is a unique identifier

  • Actual symbol is something like a 40-character random string, and the descriptor just represents it for ease of reading

  const classRoom = {
    [Symbol('Mark')] : { grade: 50, gender: 'male' },
    [Symbol('Olivia')]: { grade: 80, gender: 'female' },
    [Symbol('Olivia')]: { grade: 83, gender: 'female' },
  };

  for (const person in classRoom) {
    console.log(person); //Nothing!
  }
  • Symbols cannot be iterated over
  • Good example since people in a group need a unique identifier but could have the same name
    • Ensures keys in an object are absolutely unique
  • Potentially useful for storing private data?
const syms = Object.getOwnPropertySymbols(classRoom);
const data = syms.map(sym => classRoom[sym]);
console.log(data);
  • Object.getOwnPropertySymbols() pass in object and returns array of symbols, which can then be iterated over
    • Weird work around

Module 12 - Code Quality with ESLint

39 - Getting Started with ESLint

npm install -g eslint
  • Globally installing ESLint

.eslintrc

"env": what environments will you use the JS in

"env": {
  "es6": true,
  "browser": true
},

"rules": overrides to the extended set of rules

"rules": {
  "no-console": 0,
  "no-unused-vars": 1
},
  • 0 = off, 1 = warn, 2 = error

40 - Airbnb ESLint Settings

https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb

eslint bad-code.js --fix
  • CLI allows easy fixes to be auto corrected with --fix flag

  • .eslintrc files in a project directory work well to allow each project/team its own conventions, but it's also useful to make a global .eslintrc that can serve as a default

atom ~/.eslintrc
  • System file in home directory (~)
  • This will be overridden by a project specific .eslintrc
    • ESLint will use the settings in the closest .eslintrc file. So the closest parent folder with an .eslintrc will be used before the settings in a file in the home directory

41 - Line and File Specific Settings

/* globals twttr ga */
...
ga.track();
twttr.trackConversion();
  • globals keyword in comment block at head of file allows global variables
/* eslint-disable no-extend-native */
if (!Array.prototype.includes) {
  ...
}
/* eslint-enable no-extend-native */
/* eslint-disable */
if (!Array.prototype.includes) {
  ...
}
/* eslint-enable */
  • Can disable and then re-enable a specific rule, or all linting

42 - ESLint Plugins

In .eslintrc

"plugins": ["html", "markdown"]

44 - Only Allow ESLint Passing Code into your git repos

  • Git hook = prevent code that doesn't pass a condition from being commited
    • /.get/hooks
.git/hooks/commit-msg.sample
  • Remove .sample from file name
  • Replace sample text in that file with Bos's script in 12 - Code Quality with ESLint/commit-msg.txt
  • This will prevent a commit to that repo if ESLint has errors

Module 13 - JavaScript Modules and Using npm

45 - JavaScript Modules and WebPack 2 Tooling Setup

Decent demo but Webpack and Babel versions need to be tied to earlier releases.

  • Need to look into "zero-config" Webpack 4 for current standards
  • es6-module-instructions.md have packages, etc.
  • Big picture is that modules are files with one or more functions that can be made available to other modules. We want to write code in modules, and Webpack provides the bundler to assemble all the modules together

(1) Make entry point in app.js

(2) Create package.json + install modules + add run scripts

  • npm init

  • npm i -s lodash

  • npm i -D lodash

    • --save-dev (-D) = tool for development and not a part of the application we ship client side
  • In package.json

    "scripts": {
        "build": "webpack --progress --watch"
    },

(3) Make webpack.config file

In webpack.config.js

const webpack = require('webpack');
  • We use CommonJS require here because Node doesn't deal with ES6 import syntax
    • Interesting to note that Webpack is running in Node rather than in the browser. Makes sense as Webpack is bundling all the code before it is delivered to the browser
const nodeEnv = process.env.NODE_ENV || 'production';
  • When we make a production build, it will be much smaller than our development build.
module: {
  loaders: [
  ]
}
  • Knowing that it can be used to accept almost anything, loaders answers: How should Webpack handle different file types?

45 - Creating your own Modules

  • Good to note that modules like these could also be published to npm, which is what we're doing at work.
  • Also, variables are scoped to their module

Two types of exports in ES6

Default exports

export default apiKey;
  • The main thing a module needs to export
  • Can be named anything on import
import apiKey from './src/config';
import alternateNameforApiKey from './src/config';

Named exports

export const apiKey = '123';
  • For methods, variables, etc.
import { apiKey } from './src/config';
  • Names must match between import and export
    • The curly braces in the syntax for importing a named export look like destructuring, but are not.
import { apiKey as key } from './src/config';
  • Renaming with as is useful if you've already got a variable using a particular namespace
const age = 100;
const dog = 'snickers';

export { age as old, dog };
  • Can export multiple things and rename

47 - More ES6 Module Practice

export default function User(name, email, website) {
  return { name, email, website };
}
  • Nice model of object literal same-name key-value syntax
import User, { createURL, gravatar } from './src/user';
  • Importing the default export as well as some named exports
import slug from 'slug';
import { url } from './config';
import md5 from 'md5';

export default function User(name, email, website) {
  return { name, email, website };
}

export function createURL(name) {
  return `${url}/users/${slug(name)}`;
}

export function gravatar(email) {
  const hash = md5(email.toLowerCase());
  const photoURL = `https://www.gravatar.com/avatar/${hash}`;
  return photoURL;
}
  • Nice little example of a tidy module

Module 14 - ES6 Tooling

48 - Tool-Free Modules with SystemJS (+bonus BrowserSync setup)

https://github.com/systemjs/systemjs

Nice model of a quick way to set up module bundling w/o Webpack overhead

  • Not for production because too slow, but useful for a quick exercise
<script src="https://jspm.io/system@0.19.js"></script>
<script>
System.config({ transpiler: 'babel' });
System.import('./main.js');
</script>
import { sum, kebabCase } from 'npm:lodash';
console.log(kebabCase('Wes is soooo cool ⛓⛓⛓⛓'));
  • Note: npm:lodash
"scripts": {
"server": "browser-sync start --directory --server  --files '*.js, *.html, *.css'"
},
  • BrowserSync start script
  • Just need to save-dev BrowserSync

49 - All About Babel + npm scripts

  • If you're not using modules, can transpile via Babel w/o Webpack
npm install babel-cli@next

Allows use of Babel 7 features

"dependencies": {
"babel-cli": "^7.0.0-beta.1",
"babel-preset-env": "^2.0.0-beta.1"
},
"scripts": {
"babel": "babel app.js --watch --out-file app-compiled.js"
},
"babel": {
"presets": [
  [
    "env",
    {
      "targets": {
        "browsers": [
          "last 2 versions",
          "safari >= 8"
        ]
      }
    }
  ]
]
},
  • The "babel" property in a package.json can accept all the same settings as a .babelrc
  • "presets" > "env" allows babel to manage the scope of transpiling required to to support designated targets.
  • As browser's change, this will automatically adapt
  • Very similar to auto-prefixer
  • "env" may be a wiser choice than "es2015" because it avoids unnecessarily transpiling JS that is already supported by most browsers https://babeljs.io/docs/plugins/preset-env/
"plugins": [
"transform-object-rest-spread"
]

50 - Polyfilling ES6 for Older Browsers

<script src="https://cdn.polyfill.io/v2/polyfill.js"></script>
  • Babel accounts for the syntax changes in ES6, but new methods like Arrary.from() are not transpiled
  • They need a polyfill

Two options

Module 15 - Classes

51 - Prototypal Inheritance Review

ES6 brought classes into JavaScript, but prototypal inheritance has long been present in JS

function Dog(name, breed) {
  this.name = name;
  this.breed = breed;
}
  • Capital 'D' because this is a constructor function
Dog.prototype.bark = function() {
  console.log(`Bark, from ${this.name}`);
}
const duck = new Dog('Duck', 'Poodle')

52 - Say Hello to Classes

Class declaration

class Dog {

}
  • Preferred syntax

Class expression

const Dog = class {

}
  • Some use cases, but generally not as common
class Dog {
  constructor(name, breed) {
    this.name = name;
    this.breed = breed;
  }

  static info() {
    console.log(`This text should not be accessible on any instance`);
  }

  bark() {
    console.log(`Bark, from ${this.name}`);
  }

  get description() {
    return `${this.name} is a good dog`;
  }

  set nicknames(value) {
    this.nick = value.trim();
  }

  get nicknames() {
    return this.nick.toUpperCase();
  }
}
const duck = new Dog('Duck', 'Poodle')
  • constructor = what happens when class is invoked and an instance is created
    • This ES6 syntax for a method without the word function was earlier modelled in video 33 on object literals
  • A static method is not accessible on instances
    • Kind of like it doesn't move from the class to instance, but stays static or in place
    • Array.of() is another instance of a static method that is not inherited by other arrays off the prototype
duck.bark()   // "Bark, from Duck"
duck.info()   // Error
Dog.info()    // "This text should not be accessible on any instance"
  • A getter is not a method but a property that is computed
  • A setter allows you to assign a property

53 - Extending Classes and using super()

class Animal {
  constructor(name) {
    this.name = name;
    this.thirst = 100;
    this.belly = [];
  }
  drink() {
    this.thirst -= 10;
    return this.thirst;
  }
  eat(food) {
    this.belly.push(food);
    return this.belly;
  }
}

class Dolphasloth extends Animal {
  constructor (name, chillFactor) {
    super(name);
    this.chillFactor = chillFactor;
  }

  slobber() {
    this.thirst += 10;
    this.chillFactor -= 10;
    return this.thirst;
  }
}

const ChillDude = new Dolphasloth('Chill Dude', 100)
  • First need to create an instance of Animal before we can access 'this' in an extended classes

    • We can do this with super()
    • super() will call whatever class you're extending
      • Here super() needs name passed in because Animal() needs a name parameter for its constructor
    • It runs the constructor function in the extended class, which in turn establishes this
    super(name)
    // equals
    Animal(name)
  • Good to note that this is happening all the time in React

import React, { Component } from 'react';

export default class Battle extends Component {
  static propTypes = {
    match: PropTypes.object.isRequired,
  }
  ...
}

54 - Extending Arrays with Classes for Custom Collections

Can extend any built-in primitives

class MovieCollection extends Array {
  constructor(name, ...items) {
    super(...items);
    this.name = name;
  }
  add(movie) {
    this.push(movie);
  }
  topRated(limit = 10) {
    return this.sort((a, b) => (a.stars > b.stars ? -1 : 1)).slice(0, limit);
  }
}

const movies = new MovieCollection('Wes\'s Fav Movies',
  { name: 'Bee Movie', stars: 10 },
  { name: 'Star Wars Trek', stars: 1 },
  { name: 'Virgin Suicides', stars: 7 },
  { name: 'King of the Road', stars: 8 }
);

movies.add({ name: 'Titanic', stars: 5 });
class MovieCollection extends Array {
  constructor(name, ...items) {
    super(...items);
  • These three lines are pretty trippy
    • The first ...items is a rest operator that accepts as many parameters as are passed in (here movie objects)
    • The second ...items is a spread operator that essentially creates a new array with the items passed in (because super is invoking the Array primitive)
      • new Array('1', '2', '3');
for (const movie of movies) {
  console.log(movie);
}
  • The new ES6 iteration method for...of is particularly useful here as the previous methods like for...in are more difficult to use resultant data from

Module 16 - Generators

55 - Introducing Generators

NOTE: I get the sense that async functions are preferred over generators to handle promise-returning functions

function* listPeople(){
  yield 'Joe';
  yield 'Abe';
  yield 'Sam';
}

const people = listPeople();

console.log(people.next()); // {value: "Joe", done: false}
  • A Generator has *, yield and next() syntax, which offers multiple returns
  • done will be true once all the yield cases have been satisfied
const inventors = [
  { first: 'Albert', last: 'Einstein', year: 1879 },
  { first: 'Isaac', last: 'Newton', year: 1643 },
  { first: 'Galileo', last: 'Galilei', year: 1564 },
  { first: 'Marie', last: 'Curie', year: 1867 },
  { first: 'Johannes', last: 'Kepler', year: 1571 },
  { first: 'Nicolaus', last: 'Copernicus', year: 1473 },
  { first: 'Max', last: 'Planck', year: 1858 },
];

function* loop(arr) {
  console.log(inventors);
  for (const item of arr) {
    yield item;
  }
}

const inventorGen = loop(inventors);
  • Here the first call of next() would log all the inventors and return/yield the first object ('Einstein')
  • Then we'd need to call next() to step through the array.
    • It's like the yield suspends within the for...of loop until done has a value of true

56 - Using Generators for Ajax Flow Control

  • One case for generators is ability to do waterfall-like Ajax requests
function ajax(url) {
  fetch(url).then(data => data.json()).then(data => dataGen.next(data))
}

function* steps() {
  console.log('fetching beers');
  const beers = yield ajax('http://api.react.beer/v2/search?q=hops&type=beer');
  console.log(beers);

  console.log('fetching wes');
  const wes = yield ajax('https://api.github.com/users/wesbos');
  console.log(wes);

  console.log('fetching fat joe');
  const fatJoe = yield ajax('https://api.discogs.com/artists/51988');
  console.log(fatJoe);
}

const dataGen = steps();
dataGen.next(); // kick it off
.then(data => dataGen.next(data))
  • Calling next() here automatically sets up the next fetch
    • So, you could include something returned from the first fetch in the query to a subsequent fetch assured that the first request would have returned before the second is issued

57 - Looping generators with for...of

function* lyrics() {
  yield `But don't tell my heart`;
  yield `My achy breaky heart`;
  yield `I just don't think he'd understand`;
}

const achy = lyrics();

for (const line of achy) {  
  console.log(line);
}
  • for...of loop allows iteration through a generator and doesn't require .next()

Module 17 - Proxies

58 - What are Proxies?

const person = { personName: 'Wes', age: 100 };
const personProxy = new Proxy(person, {
  get(target, name) {
    return target[name].toUpperCase();
  },
  set(target, name, value) {
    if(typeof value === 'string') {
      target[name] = `${value.trim()} (✂️'d)`;
    }
  }
});
  • Proxies allow us to jump in between and overwrite default operations
  • First parameter is the target
  • Second parameter is a custom handler with traps
    • Good to note that the single-word method (get(), set()) is an example of the ES6 reduced syntax for methods on object literals demoed in video 33
    • Prior to ES6 this syntax was get: function(target, name) {...}
personProxy.cool = "     hella spaces here man   "
console.log(personProxy.cool) // "HELLA SPACES HERE MAN (✂️'D)";
  • The first dot notation above is the equivalent of calling set on the Proxy, which we trap in our handler and trim
  • The second dot notation is the equivalent of calling get, which our trap transforms to uppercase

59 - Another Proxy Example

const phoneHandler = {
  set(target, name, value) {
    target[name] = value.match(/[0-9]/g).join('');
  },
  get(target, name) {
    return target[name].replace(/(\d{3})(\d{3})(\d{4})/, '($1)-$2-$3');
  }
}

const phoneNumbers = new Proxy({}, phoneHandler);
  • Standardizes phone number values
const phoneNumbers = new Proxy({}, phoneHandler);
phoneNumbers.test = '344222-7522';
console.log(phoneNumbers.test) //"(344)-222-7522"
  • Starting the proxy from an empty object "target"
  • Passing in a handler object with various traps that override built-in methods

59 - Using Proxies to combat silly errors

const safeHandler = {
  set(target, name, value) {
    const likeKey = Object.keys(target).find(k => k.toLowerCase() === name.toLowerCase());

    if (!(name in target) && likeKey) {
      throw new Error(`Oops! Looks like like we already have a(n) ${name} property but with the case of ${likeKey}.`);
    }
    target[name] = value;
  }
};

const safety = new Proxy({ id: 100 }, safeHandler);

safety.ID = 200;
const safeHandler = {
  set(target, name, value) {
    const likeKey = Object.keys(target).find(k => k.toLowerCase() === name.toLowerCase());
  • The handler, safeHandler, grabs the keys off the target object and uses the name parameter to access the key being used in the set operation
if (!(name in target) && likeKey) {
  • (name in target) is new to me. Checks if an object has a key

Module 18 - Sets and Weak Sets

61 - Sets

const people = new Set();
people.add('Wes');
people.add('Snickers');
people.add('Kait');

console.log(people.size); // 3
people.delete('Wes') ;
people.clear();
  • New primitive. Like an array, but no indexes and each item is unique

Set objects are collections of values. You can iterate through the elements of a Set in insertion order. A value in the Set may only occur once; it is unique in the Set's collection.

for (const person of people) {
    console.log(person);
  }
  • Iterate over a set with for...of
  • values() will return a SetIterator which has the same API as a generator, so can iterate with .next()
const students = new Set(['Wes', 'Kara', 'Tony']);

const dogs = ['Snickers', 'Sunny'];
const dogSet = new Set(dogs);
  • Can create a Set by passing in an array

62 - Understanding Sets with Brunch

const brunch = new Set();
brunch.add('Wes');
brunch.add('Sarah');
brunch.add('Simone');
const line = brunch.values(); //Returns a generator
console.log(line.next().value); //Steps through the generator while logging the name at the current position, which is here Wes
console.log(line.next().value); // Sarah
console.log(brunch); //Set(3) {"Wes", "Sarah", "Simone"}
console.log(line); //SetIterator {"Simone"}
  • When you call next() against a SetIterator the value at the current position will remove itself (mutation by default, weird)

63 - WeakSets

  • A WeakSet can only ever contain objects
  • Cannot loop over a weak set (no iterator)
  • Cannot use clear()
let dog1 = { name: 'Snickers', age: 3 };
let dog2 = { name: 'Sunny', age: 1 };

const weakSauce = new WeakSet([dog1, dog2]);

console.log(weakSauce);
dog1 = null;
console.log(weakSauce); //After waiting a few seconds, the weak set should have been garbage collected
  • Weak sets clean themselves up through garbage collection
    • By setting dog1 to null we've given the weak set a green light to garbage collect and automatically delete that value
    • A memory leak is when you reference something that should not be able to be referenced but hasn't yet been garbage collected
    • Hard to say exactly when garbage collection will occur because it varies between browsers and cannot be forced
    • This may be useful in the case of removing DOM nodes that are no longer needed

The WeakSet is weak: References to objects in the collection are held weakly. If there is no other reference to an object stored in the WeakSet, they can be garbage collected. That also means that there is no list of current objects stored in the collection. WeakSets are not enumerable.

Module 19 - Map and Weak Map

64 - Maps

  • Just as Sets are similar to arrays but with a twist, Maps are similar to objects (but with a twist)
const dogs = new Map();

dogs.set('Snickers', 3);
dogs.set('Sunny', 2);
dogs.set('Hugo', 10);

console.log(dogs.has('Snickers')); //true
console.log(dogs.get('Snickers')); //3
  • Simple API
    • has(), get()
dogs.forEach((val, key) => console.log(val, key));

for (const [key, val] of dogs) {
  console.log(key, val);
}
  • Two ways to iterate: forEach + for...of
for (const [key, val] of dogs) {
  • Because each element in a Map returns an array with the key and value, we can use destructuring signalled by the square brackets here to declare these as variables being pulled out of the array

64 - Map Metadata with DOM Node Keys

Use case here is storing metadata about an object, but avoiding putting the data on the object itself, which is a bit dirty and means all that data is erased if the object is erased. The metadata here is how many times the button has been clicked

const clickCounts = new Map();
const buttons = document.querySelectorAll('button');

buttons.forEach(button => {
  clickCounts.set(button, 0);
  button.addEventListener('click', function() {
    const val = clickCounts.get(this);
    clickCounts.set(this, val + 1);
  });
});
  • The keys on a Map can be objects, in this case a DOM node
    • The enhanced feature over an object literal is that the key isn't limited to being a string
clickCounts.set(button, 0);
button.addEventListener('click', function() {
  const val = clickCounts.get(this);
  clickCounts.set(this, val + 1);
  • get() and set() used this way still feels quite weird to me
  • Because this refers to both the DOM node registering the click event and the Map key, it can be seen as an accurate identifier

65 - WeakMap and Garbage Collection

let dog1 = { name: 'Snickers' };
let dog2 = { name: 'Sunny' };

const strong = new Map();
const weak = new WeakMap();

strong.set(dog1, 'Snickers is the best!');
weak.set(dog2, 'Sunny is the 2nd best!');

dog1 = null;
dog2 = null;
  • The WeakMap here allows the deleted dog2 variable to be garbage collected
    • This prevents a memory leak, where dog2 might be referenced even though it's been removed from our application. The "strong" Map, on the other hand, could expose the app to such a potential memory leak
    • Bos says WeakMap is useful when you don't want to "babysit" what is included in your Map
    • https://stackoverflow.com/questions/29413222/what-are-the-actual-uses-of-es6-weakmap
    • How does the browser know that a given object or DOM node has no references to it and can therefor be garbage collected?

Module 20 - Async + Await Flow Control

Also a great resource: https://developers.google.com/web/fundamentals/primers/async-functions

67 - Async Await - Native Promises Review

Two models of native promises

  • Each use then() + catch()

fetch

fetch('https://api.github.com/users/wesbos').then(res => {
    return res.json();
  }).then(res => {
    console.log(res);
  }).catch(err => {
    console.error('OH NOO!!!!!!!');
    console.error(err);
})
  • Fetch() is a native method that returns a promise
    • Using console.error provides more of a stack trace

getUserMedia

const video = document.querySelector('.handsome'); //<video controls class="handsome"></video>

navigator.mediaDevices.getUserMedia({ video: true }).then(mediaStream => {
  video.srcObject = mediaStream;
  video.load();
  video.play();
}).catch(err => {
  console.log(err);
})

68 - Async Await - Custom Promises Review

function breathe(amount) {
  return new Promise((resolve, reject) => {
    if (amount < 500) {
      reject('That is too small of a value');
    }
    setTimeout(() => resolve(`Done for ${amount} ms`), amount);
  });
}

breathe(1000).then(res => {
  console.log(res);
  return breathe(500);
}).then(res => {
  console.log(res);
  return breathe(600);
}).catch(err => {
  console.error(err);
})
  • We can write a function that returns a custom promise
  • reject will throw an error, which needs to be caught with catch()

69 - All About Async + Await

Almost all the time, JS is asynchronous, meaning the execution of code is not blocking other code from executing

Promises and Async + Await allow a degree of flow control

function breathe(amount) {
  return new Promise((resolve, reject) => {
    if (amount < 500) {
      reject('That is too small of a value');
    }
    setTimeout(() => resolve(`Done for ${amount} ms`), amount);
  });
}

async function go() {
  console.log(`Starting`);
  const res = await breathe(1000);
  console.log(res);
  const res2 = await breathe(300);
  console.log(res2);
  const res3 = await breathe(750);
  console.log(res3);
  const res4 = await breathe(900);
  console.log(res4);
  console.log('end');
}

go();
  • Within an async function, we can use await which allows a promise to be resolved or rejected prior to moving on

70 - Async + Await Error Handling

Basic try/catch

async function go(firstName, lastName) {
  try {
    console.log(`Starting for ${firstName} ${lastName}`);
    const res = await breathe(1000);
    console.log(res);
    const res2 = await breathe(100);
    console.log(res2);
    console.log('end');
  } catch(err) {
    console.error(err);
  }
}
  • Use case: You need to handle an error unique to this function
    • For instance, in a form

Higher-order function for error handling

function breathe(amount) {
  return new Promise((resolve, reject) => {
    if (amount < 500) {
      reject('That is too small of a value');
    }
    setTimeout(() => resolve(`Done for ${amount} ms`), amount);
  });
}

function catchErrors(fn) {
  return function(...args){
    return fn(...args).catch((err) => {
      console.error(err);
    });
  }
}

async function go(firstName, lastName) {
  console.log(`Starting for ${firstName} ${lastName}`);
  const res = await breathe(1000);
  console.log(res);
  const res2 = await breathe(100);
  console.log(res2);
  console.log('end');
}

const wrappedFunction = catchErrors(go);

wrappedFunction('Joe', 'Winner');
  • Use case: You need a general error handler that can be reused on various functions
  • The wrapped function has error handling "sprinkled onto it" by the higher-order function
function catchErrors(fn) {
  return function(...args){
    return fn(...args).catch((err) => {
      console.error(err);
    });
  }
}
  • The first ...args is a rest operator and the second ...args is a spread operator
    • This allows us to pass in any number of parameters to the wrapped function

71 - Waiting on Multiple Promises

We want multiple fetches to go out into the world rather than waiting for each one to return before the next is issued. So, this would most likely be bad:

async function go() {
  const p1 = await fetch('https://api.github.com/users/wesbos');
  const p2 = await fetch('https://api.github.com/users/stolinski');

Method one

async function go() {
  const p1 = fetch('https://api.github.com/users/wesbos');
  const p2 = fetch('https://api.github.com/users/stolinski');
  const res = await Promise.all([p1, p2]);
  const dataPromises = res.map(r => r.json());
  const [wes, scott] = await Promise.all(dataPromises);
  console.log(wes, scott);
  // const people = await Promise.all(dataPromises); // Alternative if we don't know how many people being returned  
}
go();
  • Need two await Promise.all when handling fetched data
const [wes, scott] = await Promise.all(dataPromises);
  • Destructing two immediately declared variables from the array returned by Promise.all()

Method two

async function getData(names) {
  const promises = names.map(name => fetch(`https://api.github.com/users/${name}`).then(r => r.json()));
  const people = await Promise.all(promises);
  console.log(people);
}

getData(['wesbos', 'stolinski', 'darcyclarke']);
  • Can chain the fetch() and then() calls together
    • I like 👆

Related example from Jake Archibald:

async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}

https://developers.google.com/web/fundamentals/primers/async-functions

72 - Promisifying Callback Based Functions

// navigator.geolocation.getCurrentPosition(function (pos) {
//   console.log('it worked!');
//   console.log(pos);
// }, function (err) {
//   console.log('it failed!');
//   console.log(err);
// });

function getCurrentPosition(...args) {
  return new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(...args, resolve, reject);
  });
}

async function go() {
  console.log('starting');
  const pos = await getCurrentPosition();
  console.log(pos);
  console.log('finished');
}

go();
  • Allows us to make a callback-oriented API work on promises
  • 3rd party options also, including a built-in Node utility

Module 21 - ES7, ES8 + Beyond

73 - Class Properties

class Dog {
  constructor(name, breed) {
    this.name = name;
    this.breed = breed;
  }

  barks = 0;

  bark() {
    console.log(`Bark Bark! My name is ${this.name}`);
    this.barks = this.barks + 1;
  }
}
  • Declaring barks outside constructor is an example of a class property
  • Commonly used in React
class Mouse extends React.Component {
 static propTypes = {
   render: PropTypes.func.isRequired
 }

 state = { x: 0, y: 0 }
 ...
 }

https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce

Needs this Babel transform https://babeljs.io/docs/plugins/transform-class-properties/

74 - padStart and padEnd

The padStart() method pads the current string with another string (repeated, if needed) so that the resulting string reaches the given length.

"1".padStart(3, 0) // 001
  • Useful if trying to right align text to the longest string in a series
const strings = ['short', 'medium size', 'this is really really long', 'this is really really really really really really long'];
const longestString = strings.sort(str => str.length).map(str => str.length)[0];

strings.forEach(str => console.log(str.padStart(longestString)));

75 - ES7 Array.includes() + Exponential Operator

Array.includes()

['a', 'b', 'c'].includes('c') //true

Exponential Operator

3 ** 3 //27

76 - Function Arguments Trailing Comma

const names = [
  'wes',
  'kait',
  'lux',
  'poppy',
];

const people = {
  wes: 'Cool',
  kait: 'Even Cooler!',
  lux: 'Coolest',
  poppy: 'Smallest',
  snickers: 'Bow wow',
}

function family(
  mom,
  dad,
  children,
  dogs,
) {

}
  • Just as with arrays and objects, function arguments can accept trailing commas
  • Rationale is that someone contributing will not have a line they're not really associated with come up as their edit just because they added a comma
  • Typically best to just add rule to ESLint and Prettier

77 - Object.entries() + Object.values()

const inventory = {
  backpacks: 10,
  jeans: 23,
  hoodies: 4,
  shoes: 11
};

// Make a nav for the inventory
const nav = Object.keys(inventory).map(item => `<li>${item}</li>`).join('');
console.log(nav);

// tell us how many values we have
const totalInventory = Object.values(inventory).reduce((a, b) => a + b);
console.log(totalInventory);

// Print an inventory list with numbers
Object.entries(inventory).forEach(([key, val]) => {
  console.log(key, val);
});

for (const [key, val] of Object.entries(inventory)) {
  console.log(key);
  if (key === 'jeans') break;
}
  • Object.values() is like the other side of Object.keys()
  • Object.entries() returns each point in the object as a two-value array
  • Benefit of for...of is the ability to break out of it, which cannot be done in map or forEach