Guide to ES6

Head Organizer, JavaScriptLA, HackBuddy.com

JOIN US ON DISCORD OR SLACK!

Also, you can follow us on all major social media:

 

Facebook | Instagram | Twitter | Linkedin

FOLLOW US ON YOUTUBE FOR NEW VIDEOS

WE ALSO BLOG:

Agenda

  • The Basics / Tooling
  • Variables
  • Functions, Arrow Functions
  • Spread / Rest Operators
  • Destructuring, Classes
  • Promises/Generators / Iterators
  • Fetch / Async / Await
  • Sets / Maps
  • JavaScript Modules
  • Decorators 
  • Using TypeScript

Getting Started / Tooling

Lots of different ways to get started, but we'll use Babel & Webpack as our starter kit.

https://github.com/vijayxtreme/starterES6Webpack 

Alternatively, you can use another toolkit like Gulp/Traceur, Rollup, or even just try out ES6 in your browser if it supports the syntax (e.g. Google Chrome)

Unfortunately, tooling can sometimes be the biggest handicap to learning new JavaScript

 

Variables

let
const
var

 

Use let when you want to have block scope; use const when you want to make sure your variable value can't be overridden.


When would using var still be appropriate?

CODE SAMPLES

let x = "Hello" 

if(x == "Hello") {
  let y = " World" 
}

console.log(x + y) 
let x = "Hello"

if(x == "Hello") {
  let y = " World" 
  console.log(x + y) 
}

What do you expect as the output here?

What do you expect as the output here?

CODE SAMPLES

const $ = function jQuery(){ //do jquery stuff }

/*** adding in other libraries into our JavaScript  ***/


$ = function angular(){ //do angular stuff }

console.log($)

What do you expect as the output here?

const API_KEY = '123456'

API_KEY = '654321'; //someone tries to change this variable's value

What do you expect as the output here?

TEMPLATE STRINGS

Template strings are great for concatenating lots of variables with text.  In the old days of ES5, we had to do stuff like this:

 

 

 

Now we can use the backtick symbol ` to start and end a template string, and use ${} syntax to plug in variables like so:

var x = 10, y=20, a=40, b=15;
var result = "Robert has " + x + " apples, " + y + " bananas, " + a + " mangoes, " + b + " ice creams." ;
let x = 10, y = 20, a = 40, b = 15

let result = `Robert has ${x} apples, ${y} 
bananas, ${a} mangoes, ${b} ice creams`

Result is cleaner, easier to read, write syntax; and template strings don't care about multiple lines!

TEMPLATE STRINGS AS FUNCTIONS

let parseTemplate = function(text, variable1, variable2){
  console.log(text);
  console.log(variable1);
  console.log(variable2);
  return variable2;
}

let item1 = "Laundry"
let item2 = function(){ return "Groceries" }

let result = parseTemplate`I have to do my ${item1} and ${item2}`

console.log(`Finished with ${result()}`)

GRAPHQL USING TEMPLATE STRINGS

export const query = graphql`
  query {
    allWordpressPost {
      nodes {
        title
        excerpt
        link
        featured_media {
          source_url
        }
      }
    }
  }`

Don't freak out if you don't understand this yet, just try to get the general idea :)

ARROW Functions

Function expressions that define functions can now be replaced with "arrow function" syntax.  Here's single parameter and multiple parameters below.   

Note: If you have no params, write your function like so: () => { /*My code here */}

let greetName = name => `hello ${name}` 

// ES5 syntax
var greetName = function(name) {
  return "hello " + name; 
}

console.log(greetName('vijay'))
//hello vijay
let z = (x, y) => x + y

//ES5 syntax
var z = function(x, y){
  return x + y;
}

CODE SAMPLES

let greetAndPrintName = name => {
  let str = `hello ${name}`;
  console.log(str);
}

greetAndPrintName('vijay')

Example of a multi-line function; Wrapped with {}

let result = (x,y) => (
  x + y 
)

console.log(result(1,2)) //3

Example of a multi-line return statement; Wrapped with ()

ADDITIONAL NOTES ABOUT ARROW FUNCTIONS

  • Arrow Functions have a  "lexical" this value
  • this usually refers to the object's context (where does it exist in scope)
  • Typically in JS, when we add new variables they get attached to global scope, a function's scope, or in an object that they were created from -- context depends on how the variable gets created.   
  • We run into issues when we want to pass the value of this to another function, in these cases we use bind() in ES5 to bind the current value of this for use inside a nested function (aka var that = this).
  • Arrow functions remember the context they were created in, so even as we pass this to nested functions, this is the same value as the arrow function it was created with, hence we don't need to worry about binding with arrow functions.

CONFUSING ES5

function Mammal(name) {
    this.animalName = name || "Henry";
    this.sayName = function() {
        setTimeout(function() {
            console.log("My name is " + this.animalName);
        }, 200);
    }
}

var dog = new Mammal();
dog.sayName();
//My name is undefined

How would we fix our code in ES5?

CONFUSING ES5

function Mammal(name) {
    this.animalName = name || "Henry";
    this.sayName = function() {
        var that = this;
        setTimeout(function() {
            console.log("My name is " + that.animalName);
        }, 200);
    }
}

var dog = new Mammal();
dog.sayName();
//My name is Henry

One possible solution, use that.   

Er.. um, this and that?

CONFUSING ES5

function Mammal(name) {
  this.animalName = name || "Henry";
  this.sayName = function() {
     // var that = this;
      setTimeout(function() {
          console.log("My name is " + this.animalName);
      }.bind(this), 200)
  }
}

var dog = new Mammal();
dog.sayName();
//My name is Henry

How about bind instead?

NAH, LET'S JUST USE ARROW FUNCTIONS

class Mammal {
    constructor(name = "Henry") {
        this.animalName = name;
    }
    sayName() {
        setTimeout(() => {
            console.log(`My name is ${this.animalName}`)
        }, 200)
    }
}
let dog = new Mammal();
dog.sayName();

Yay, now I don't have to trip over my own code!

 

Remember, arrow functions remember their scope, they have a lexical scope binding.

REACTJS CODE WITH AND WITHOUT BINDING

import React, { Component } from "react";
import "./styles.css";

export default class App extends Component {
  constructor() {
    super();
    this.clickMe = this.clickMe.bind(this); //headache -- use arrow functions instead!!!
  }
  clickMe() {
    alert("hello you clicked me");
  }
  render() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <button onClick={this.clickMe}>Click Me</button>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);

Don't worry if you don't understand this right now, just the gist is fine.

REACTJS CODE WITH AND WITHOUT BINDING

import React, { Component } from "react";
import "./styles.css";

export default class App extends Component {
  constructor() {
    super();
  }
  render() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        <h2>Start editing to see some magic happen!</h2>
        <button
          onClick={() => {
            /* Wow less code bloat!! */
            alert("hello you clicked me");
          }}
        >
          Click Me
        </button>
      </div>
    );
  }
}

Lexical arrow functions to the rescue!

Use them like super powers!  Play around with the sandbox at:

https://codesandbox.io/s/silent-sun-thrji

Rest Operator

(...param) => param

Returns an array of arguments passed to function at run time.

 

Note, ...arg must always be the last argument, otherwise this will not work.

(...param, param2) //SyntaxError
(param, ...param2) //Correct
let sum = (total = 0, ...numbers) => {
  numbers.forEach(num => total += num)
  console.log(total);
}

sum(1,2,3,4,5,6,7);
sum(1,2,3,4,5,6);
sum(1,2,3);

Spread Operator

Spread operator plugs in values 1,2,3 into a,b,c respectively.

How would you do this in ES5?

 

The spread operator is used for arguments in a function call, while the rest operator is used as parameters in a function definition

let sum = (a,b,c) => {
  console.log(a + b + c)
}
sum(...[1,2,3]) //spread operator 

Objects Destructuring

What's the output of myVariable?

 

How about here?

 

 

Why do you think destructuring is valuable?

let { myVariable } = { 
  myVariable: "green Salad"
}
let [a,b] = [3, (x,y)=>x+y, 3]

Classes

How does this compare to prototypes?

Do you remember what a prototype is? 

When is it better to use a class vs a function?

class Animal {
  constructor(name="", species=""){
    this.name = name
    this.species = species
  } 
}


class Dog extends Animal {
  constructor(name, species){
    super(name,species)
  }
 
  bark(){
    console.log(`${this.name}, a ${this.species} says woof woof`)
  }
}

const fido = new Dog("Fido", "Dog")
fido.bark()

Promises

Avoid the common issue of "callback hell" in ES5; aka nested callbacks -- difficult to read and debug.

If you are doing work with RESTful services, use Promises instead

let p = new Promise((resolve, reject) => {
  
  setTimeout(() => {
    let rand = Math.floor((Math.random()*10),1), x = false
    
    x = (rand % 2 == 0) ?  true : x
    console.log(rand)
     
    x ? resolve() : reject()

  }, 1000) 
})

p.then(() => {
  console.log(`Life's great`)
})
.catch(e => console.log(`You can't always get what you want`))

AJAX EXAMPLE

let ajaxP = (http) => {
    return new Promise(function(resolve, reject) {
        if (!http) {
            throw new Error("Must define an http object")
        }
        let xhr = new XMLHttpRequest();
        xhr.open(http.type, http.url);
        xhr.onload = function() {
            if (this.status >= 200 && this.status < 300) {
                resolve(xhr.response);
            } else {
                reject({
                    status: this.status,
                    statusText: xhr.statusText
                });
            }
        };
        xhr.onerror = function() {
            reject({
                status: this.status,
                statusText: xhr.statusText
            })
        }
        xhr.send();
    });
}

AJAX EXAMPLE (CONTINUED)

//Promisified version of our http request
ajaxP({
    type: "GET",
    url: "https://jsonplaceholder.typicode.com/todos/1"
}).then(response => {
    return JSON.parse(response);
}).then(response => {
    console.log(response)
    let user = response.userId
    let url = `https://jsonplaceholder.typicode.com/users/${user}`

    return ajaxP({
        type: "GET",
        url: url
    })
}).then(res => {
    console.log(JSON.parse(res))
}).catch(e => console.log(e))

The basic pattern here is:

  • Do something AJAXy
  • Then do something else AJAXy... Then do something else AJAXy...
  • Catch any errors :) -- is this a better pattern than callback hell?

FETCH (SIMPLIFIES AJAX & PROMISES)

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => response.json())
  .then(json => console.log(json))

No need to write all the complexities of AJAX and error handling with XMLHttpRequest; the community already did it for you!

 

If you understand promises, fetch works exactly the same way.  Check out the API!


You can replace JQuery $.ajax with fetch and avoid callback hell!  Take the plunge today~

ASYNC & AWAIT

function promiseMeYoullWait() {
    return new Promise((resolve, reject) => {
        try {
            setTimeout(() => {
                resolve('You waited!')
            }, 4000)
        } catch (e) {
            reject(`Sorry the program couldn't wait, Error: ${e}`)
        }
    })
}

//Start here when you read this code...
async function myAsync() {
    let result = await promiseMeYoullWait()
    console.log(result)
}

myAsync(); //You waited!

async & await allow us to write code that looks more understandable to a human; step by step, even though this code is still asynchronous -- it looks synchronous to us. 

Why do you think this is important?

Iterators

What does it mean to be an iterable object?

 

What does it mean to iterate something?

 

 

ITERATOR CODE SAMPLE

let myArr = [1,2,3,4]

let itr = myArr.values()  //get our iterator from our array

let i = itr.next();  //call the next method on the iterator

while(!i.done){ 
  console.log(i);  //show the value and done for the current object
  i = itr.next();  //keep calling the iterator next method
}

An array is an iterable object; we can loop through it (it supports iteration)

An iterator helps us loop through the code, one step at a time.   So think of a for loop that stops each time and gives you a result before going to next iteration.

Why might this be useful?

Generators

Generators yield.

 

Think a function that returns new results each time, rather than the same expected result over and over.


Generators return iterable objects, which give us access to new yield values

GENERATOR CODE SAMPLE

function* myGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

let itr = myGenerator(); //generator function call, creates a new iterator, itr
let res = itr.next(); //we get an object with { value: 1, done: false }

console.log(res.value, res.done); //1, false
// do something else


res = itr.next();
console.log(res.value, res.done); //2, false
// do something else

res = itr.next();
console.log(res.value, res.done); //3, true

 A generator returns an iterable object with keys for value and a done flag.  In each yield, we get the value and a done (true/false).  So long as done is not true, we can keep iterating through the generator for new yield values

GENERATOR CODE SAMPLE

function* createNewId() {
    let i = 0;
    while (true) { //generate infinite ids
        yield i;
        i++;
    }
}

let itr = createNewId() //create iterator

function createNewAccount(email) {
    let res = itr.next(); //get next yield value
    let id = res.value; //store yield value in id
    let user = {
        email,
        id
    }
    console.log("new user created", user)
}

createNewAccount("aj@iw.org") 

createNewAccount("ma@nn.com")

createNewAccount("pjw@sn.com")

This code can generate us new accounts forever, but we use iterators to control how often we need to yield

GENERATOR CODE SAMPLE

function* myGenerator(){
  yield 1;
  yield 2;
  yield 3;
}


for(let i of myGenerator()){
  console.log(i)
}

You can use the for...of loop for convenience (syntactic sugar) instead of calling myGenerator.next()

MAPS & SETS

Maps can hold values of any type and they also remember the order in which items were individually stored

 

Sets hold unique values; when do you think a Set might be useful over an array?

MAP EXAMPLE

//Map Example
let myMap = new Map();

myMap.set('John', 'Cheeseburger');
myMap.set('Vijay', 'Chicken Tikka Masala')
myMap.set('Danielle', 'Vegan Lasagna')
myMap.set('function doNothing(){}', 'Did nothing')
myMap.set(NaN, 'yup NaN works too')

console.log(myMap.size);

myMap.forEach((val, key) => console.log(`${key}: ${val}`))


The first argument is your "key", the second argument is your "value".  Think of Maps like hashes, yet you can store any type you want as a key, and look it up in the same fashion to retrieve a value.

 

When do you think a Map might be better than a traditional object for item lookup?

SET EXAMPLE

let mySet = new Set();

//use the add method to add new values to our set
mySet.add(1)
mySet.add(2)
mySet.add((24/12))
mySet.add("iPhone")

//use the delete method to remove values from our set
mySet.delete(1)

console.log(mySet.has(2)) //true
mySet.forEach(val => console.log(val))
//2
//iPhone
console.log(mySet.size) //2

Sets must always have unique values, unlike arrays which can hold duplicate values.  Why might a Set be better than an Array?

JavaScript Modules

  • Before JavaScript Modules, how did one "import" and "export" code?  

 

  • What's the difference between AMD and CommonJS?  Which one is more prevalent?

 

  • How about UMD?

OLD WAY OF MODULING

//new Q library
var Q = (function(){
   return {
 	log: function(query){
   	  console.log(query)
 	},
 	ver: 1.0
   };
}())


//version 2 update
(function(lib){
  if(lib.ver < 2) {
	update(lib);
  }else {
	console.log("Library at latest version")
	return lib;
  }

  function update(lib){
	lib.add = function(num1, num2){
  	  lib.log(num1 + num2);
	}
	lib.subtract = function(num1, num2) {
  	  lib.log(num1 - num2);
	}
	lib.ver = 2.0
  }

  return lib;
})(Q);

ES5 -- LET'S COMPOUND SCRIPTS! YAY

<!DOCTYPE html>
<html lang="en" dir="ltr">

<head>
    <meta charset="utf-8">
    <title></title>
</head>

<body>
    <script type="text/javascript" src="Q.js"></script>
    <script type="text/javascript" src="Q.update.js"></script>
    <script type="text/javascript" src="Q.random.js"></script>
    …
    <script type="text/javascript" src="Q.update100.js"></script>
    <script type="text/javascript" src="Q.plugin45.js"></script>
    <script type="text/javascript" src="Q.randomN.js"></script>
</body>

</html>

Hmm.. this is getting heavy.. I wonder if our site will be okay with all these requests.  Perhaps we could move to CDN?  Could we minify all these into one file?  Would that be optimal?  What if we don't need all these libraries, should we just rewrite? 

AMD TO RESCUE... MAYBE USING REQUIREJS

<html>

<head>
    <title>My Sample Project</title>
    <!-- data-main attribute tells require.js to load scripts/main.js after require.js loads. -->
    <script data-main="scripts/main" src="scripts/require.js"></script>
</head>

The data-main attribute for the script tag tells require.js to load main.js after require.js loads.  (The require.js library is downloaded from the RequireJS website).   

The main.js file is where you can tell RequireJS to load in any other dependencies you need for your program.  Modules are asynchronously loaded by RequireJS, hence Asynchronous Modular Development.  Key benefit -- module loading for the browser (no NPM needed)

//Q.js file
define(["jquery"], function($) {
    return {
        log: function(val) {
            $("body").html(val)
        },
        ver: 1.0
    }
});
project folder >
|  js
   | lib
     - Q.js
   - jquery.js
   - main.js
   - require.js
   index.html

HOW ABOUT COMMONJS INSTEAD? NPM?

/***** q.js *****/
function log(query) {
  console.log(query)
}

var version = 1.0;

module.exports =  {
 	log: log,
 	ver: version
};


  • Code can now live in its own file and is scoped only to that file
  • q.js lives in its own file, we add what we need to module.exports global, which node will pull into other files, like app.js
  • app.js understands the require statement, and can get at the Q.js code.   
  • Using tools like grunt, gulp, we can minify, uglify all this code down into one file that our index.html uses.   Key benefit -- easy workflow, can use with NPM ecosystem
/***** app.js *****/
var Q = require("Q.js");
console.log(Q.ver) //1.0
project folder >
|  js
   | q.js
   | app.js
   index.html

WHY NOT BOTH?

How many times have you seen this ad?

UMD - IF COMMONJS - USE IT, ELSE AMD, ELSE OLD WAY PLS.K.THX.BYE

(function(root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['Q'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        module.exports = factory(require('Q'));
    } else {
        // Browser globals
        root.Q = factory(root.Q);
    }
}(this, function(Q) {
    Q.add = function(num1, num2) {
        Q.log(num1 + num2);
    };

    return Q;
}));

Yeah, um.. no.. can we just go back to ES6 already?

ES6 GIVES YOU

  • CommonJS syntax out of the box.
  • Simpler/easier to read/write/execute
  • import, export
  • (replaces require, module.exports)
  • Remember though, you still need to transpile ES6 code even with Node.js 
  • If you want to use AMD w/ES6 syntax instead, you can provided you transpile your code with Babel in a script tag (and include the Babel script first)
  • However, all this said -- exporting/importing got a wholeeee lot easier!!!  Thank you ES6 overlords!

ES6 IMPORT & EXPORT

/*** Car.js our Module file ***/
let car = "Toyota"
export default function driveMyCar() {
    return `${car} is driving`
}

/*** index.js ***/
import driveMyCar from "Car"

let myCar = driveMyCar()
console.log(myCar)

Yay, so much simpler.. and we can use things like Webpack to bundle all our modules together, split the code into vendor files, our files, then cacahe redundant files, eliminate unused code, making our overall code easier to track/ship, generate less requests, and work on devices without much reliable internet giving our users a better user experience no matter where they are!!!!!!!  (Okay sorry for making this complex again by mentioning Webpack lol).

 

See our talk on Webpack 4 on YouTube for more info (if interested in that rabbit hole)

ESNext

Contrary to popular belief, ES7, ES8, ES2020 **don't** exist.
   

New ideas transition in proposal stages via TC39 (panel that meets on EcmaScript).

 

Babel makes it possible for you to try new syntax (but don't depend on it in production)!

Let's use Decorators from Stage-2 

Decorators are like functions that augment other functions (sometimes called wrappers).

 

You see them a lot in Angular syntax.


 

DECORATOR EXAMPLE

function log(User) {
    return (...args) => {
        console.log(`New user created ${args}`);
        return new User(...args);
    };
}

@log
class User {
    constructor(name, age) {
        console.log(`New user entered the chat: Name: ${name}, Age: ${age}`)
    }
}

const u = new User('Graham', 34);

The idea with decorators is to make it so we can bury "startup" code elsewhere and just use "annotation" style syntax to augment classes with special powers -- in this case log every User

ANGULAR EXAMPLE OF USING DECORATORS

/* These are JavaScript import statements. Angular doesn’t know anything about these. */
import {
    BrowserModule
} from '@angular/platform-browser';
import {
    NgModule
} from '@angular/core';

import {
    AppComponent
} from './app.component';

/* The @NgModule decorator lets Angular know that this is an NgModule. */
@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [ /* These are NgModule imports. */
        BrowserModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule {}

Can you see how a decorator augments our AppModule class here?

Want more on Angular?  Check out Angular presentation here.

Using TypeScript

TypeScript is a language offered by Microsoft that adds types to JavaScript; much like you have types in other programming languages like Python, C++, Java.

 

Common types: ints, booleans, floats, decimals, strings (stack variables), chars, and heap variables (objects).  You can also use TS with ES6, React, etc by configuring a simple tsjson.config file


Types can be useful, especially when dealing with memory; you could hope that JS does its job, but you never know what monsters lurk under the hood.

Using TypeScript W/WEBPACK

Git repo set up for you to play around:

 

https://github.com/vijayxtreme/starterTSWebpack

 

Documentation on Config for Webpack and TS at:

https://webpack.js.org/guides/typescript/

 

Full blog post on TypeScript here:

https://javascriptla.net/blog/intro-to-typescript/

SAMPLE TS CODE

class Student {
    fullName: string;
    constructor(public firstName: string, public middleInitial: string, public lastName: string) {
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
}

interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = new Student("Jane", "M.", "User");

document.body.textContent = greeter(user);

Note the use of ES6 with types (stricter JS here)

Source: https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html

THANK YOU!

If you were overwhelmed (lol) and want to visit this again with me holding your hand even further, sign up for the video lecture at:

https://hackbuddy.teachable.com

(8 hours of video)

 

You can also get the book on Amazon.com (if you like reading):

https://amzn.to/2PMNrsE

 

Your purchases help keep the group going and generating more helpful content, lectures, meetups and interviews!

Made with Slides.com