Skip to content

Instantly share code, notes, and snippets.

@wktdev
Created August 24, 2018 20:15
Show Gist options
  • Save wktdev/43f16a2a6cb8b0c40c372b789cc4cb3c to your computer and use it in GitHub Desktop.
Save wktdev/43f16a2a6cb8b0c40c372b789cc4cb3c to your computer and use it in GitHub Desktop.

ECMAScript 6 (ES6)

The technical name of the JavaScript language is ECMAScript. ECMA stands for the European Computer Manufacturers Association. This is the organization that oversees the standardization process of the JavaScript language.ECMAScript version 6 (ES6 for short) is a newer version of JavaScript. In this module, you will learn about some of the features of ES6 including:

  • Imports/Exports
  • Default function arguments
  • The let and const keywords
  • Template literals
  • The rest parameter
  • The spread operator
  • Destructuring
  • Set
  • Weak Set
  • Map
  • Weak Map
  • Arrow functions
  • The class keyword
  • Promises
  • Symbols (JavaScripts 7th data type)
  • Iterables and iterators
  • Generators

To explore a full list of ES6 features, go to this link: http://es6-features.org

Backward Compatibility

When developing websites that use ES6 features you will have to compensate for older browsers that don't support these features. The solution to this problem is to use a tool called a transpiler. JavaScript transpilers convert newer versions of JavaScript to older versions of JavaScript and insure that the code executes properly on older browsers. A transpiler requires the setup of an independent web server and this is beyond the scope of this module.

Imports / Exports

items.js

export function greeting(){
    return " hi there "
}


export const name = "Bob Whatever"

export let names = ["Bob","Joe","Cindy"]

export default "the default content"

app.js

import  content, {greeting, name, names} from './items';

Default Function Arguments

In ES6 default function arguments are available. To use them you assign variables in the parameter values of your functions.


"use strict";

function addValues(a = 1, b = 2, c = 3){  // Assign variables

    return a + b + c                      // Defaults to 1 + 2 + 3
}

console.log(addValues()); // 6

console.log(addValues(5, 5)); // (5 + 5 + 3) === 13


If you invoke a function and set an argument value to undefined then the default parameter will be used.

"use strict";

function addValues(a = 1, b = 2, c = 3){

    return a + b + c
}

console.log(addValues(2, undefined, 2)); // (2 + 2 + 2) === 6

Block Scope Using let

In previous modules, you learned that JavaScript has global and function scope. This is true for ES5. ES6 has block scoped variables using the let and const keywords. You might be wondering why we didn't mention this earlier. The reason for learning ES5 first is because it is the JavaScript version most used in production and it is better to learn it first prior to moving to ES6.

How the let Keyword Works

A block scoped variable can be created by prefacing the variable with letinstead of var. The word "block" refers to the curley braces of a programming construct such as an if statement, a for-loop or function.The following code demonstrates a block scoped variable inside of an if statement using the let keyword.

"use strict";

let isTrue = true;
let someData = "blah";


if(isTrue){
    let someData = "different data"; /* This does not change the variable of the same name outside the block */
    console.log(someData) // different data
}

console.log(someData); // blah

The same code using the var keyword references the variable outside of the if statement.

"use strict";

var isTrue = true;
var someData = "blah";


if(isTrue){
    var someData = "different data"; /* References the variable outside the block  - and changes it */
}


console.log(someData); // different data

Using let Prevents Accidental Variable Overwrites

If you declare the same variable using var more than once in the same scope the variable is over written. If you do the same thing using let an error is thrown. This is a good thing because it acts as a warning that a variable of the same name has already been declared in the current scope.

var stuff = "Some stuff";

var stuff = "Some stuff";  //overwrites previous variable named "stuff"

let moreStuff = "more stuff";

let moreStuff = "some more stuff"; // Error! Already declared!

Variables declared with let are reassignable.

let moreStuff = "more stuff";

moreStuff = "more stuff"; // no error, works just like var

The const Keyword

Like the let keyword the const keyword (short for constant) creates block-scoped variables. The difference between let and const is that const declared variables cannot be reassigned. Here is an example:

"use strict";

const someData = "blah";

console.log(someData); // blah

someData = "something else"; // Error! Cannot be reassigned

Prefacing your variables with the const keyword is a good idea when they should not be changed. An example is the value pi.

const pi = Math.PI;

Template Literals

Template literals let you embed variable data in strings. To use a template literal you replace the single or double quotes of a convention string with backticks and place a variable in curley braces preceded by a dollar sign.

"use strict";

let user = "Cindy";

let greetUser = `Hello ${user}`;  

console.log(greetUser)  // Hello Cindy

The Rest Parameter

The rest parameter allows you to access an undefined number of arguments in a function as a collection in an array. The syntax is expressed as three period dots followed by a name. The following code demonstrates how to use the parameter:

"use strict";

function stuff(...args){  // Three dots and a name (args)

    console.log(args); // log name
}

stuff(1,2,3)// [1, 2, 3] is logged to the console

If you define arguments before the rest operator they will not be included in the array. Here is an example:

"use strict";

function add(a,b,...args){

    console.log(args);

    return a + b; 
}

add(1,2,3)// [ 3 ] is logged to the console

The Spread Operator

The spread operator is representend by three dots.

...

Technically, the spread operator lets you "split an iterable object into individual values". An iterable object is defined as an object that implements the ES6 iteration protocol.

In a practical sense it is useful to think of the spread operator as a way to split an array into individual values, like this:

"use strict";

var names = ["Bonnie","Dana","Lynda"];
var arr = [1,2,3,...names,4,5];

console.log(arr);  // [ 1, 2, 3, 'Bonnie', 'Dana', 'Lynda', 4, 5 ]

You can use the spread operator to split an array to function arguments.

"use strict";

var names = ["Bonnie","Dana","Lynda"];


function complimentMakers(a,b,c){

    return a + " is great!" +" "+ b + " is great! " + c + " is great!"
}


console.log(complimentMakers(...names)); // Bonnie is great! Dana is great! Lynda is great!

Destructuring

Destructuring lets you extract data from arrays or objects and assign each value to distinct variables. To do this you use the destructuring assignment expression.

Destructuring an array

In ES5 if you want to assign individual elements of an array to distinct variables you would do this:

var things= ["item-1","item-2","item-3"];

var thingOne = arr[0];
var thingTwo = arr[1];
var thingThree arr[2];

In ES6 you do this:

"use strict";

let things = ["item-1","item-2","item-3"];

let a, b , c;

[a, b, c] = things;   /* This is the destructuring assignment expression
                         The brackets to the left of the assignment operator are not an array.
                      */

console.log( a ); // item-1
console.log( b ); // item-2
console.log( c ); // item-3

Destructuring an object

In ES5 if you want to assign individual object properties to variables you do this:

"use strict";

var user = {

    name:"Bonnie",
    age:60,
    profession:"Antique dealer"
}

var name = user.name;
var age = user.age;
var profession = user.profession;


In ES6 you do this:

"use strict";

let user = {

     name:"Bonnie",
     age :60,
     profession:"Antique dealer"
}


let name, age, profession;        // You have to declare the variables first

({name, age, profession} = user); // Variable names must match property key names

console.log( name );       // Bonnie
console.log( age );        // 60
console.log( profession ); // Antique dealer


Set

A set is list of values without duplicates. You create a new set using the Set constructor.

"use strict";

var items = new Set();

You add items to the set either on initialization by using array syntax, or by using the add method.

"use strict";

var items = new Set([1,2,3,4]); // add items to set

items.add(5)                    // add one more item

console.log(items);             // Set { 1, 2, 3, 4, 5 }

If you add duplicate values they will be removed.

var items = new Set([1,2,3,4,5,5,5,5]); // add items to set

console.log(items);             // Set { 1, 2, 3, 4, 5 }

To determine how many items a set contains you use the size method.

var items = new Set([1,2,3,4,5]); // add items to set

console.log(items.size); // 5  

If you use objects as keys they are considered unique. Therefore, you can do this:

let items = new Set([{},{},{}]); 

console.log(items.size); // 3  

To check if a set contains a value you use the has method.


"use strict";

let user = {name:"Bob"};
let items = new Set([user,"12345"]); 

console.log(items.has(user)); // true   
console.log(items.has("12345")); // true  

To remove an item from a set you use the delete method.


"use strict";

let user = {name:"Bob"};
let items = new Set([user]); 

console.log(items.has(user)); // true   

items.delete(user)            // Remove user

console.log(items.has(user)); // false  

To iterate over a Set you use a forEach method. This method takes a callback that takes three arguments. The first two arguments are the value and the third argument is the Set. The fact that the first two arguments return the same data might strike you as a bit odd. The reason for this behavior is that the forEach method that is used to iterate through arrays takes a callback that has three arguments. To keep the number of arguments consistant the Set implementation of forEach uses the first and second arguments for the same data.

let set = new Set(["item-1","item-2","item-3"]);

console.log(set);

set.forEach(function(val,index,set){

    console.log(val);        // item-1 item-2 item-3
    console.log(index);      // item-1 item-2 item-3
    console.log(set);        /* Set { 'item-1', 'item-2', 'item-3' } 
                               Set { 'item-1', 'item-2', 'item-3' } 
                               Set { 'item-1', 'item-2', 'item-3' } 
                             */
});




Weak Set

A Weak Set is like a Set except it can only have objects as keys and is not iterable. Weak Sets do not have a forEach method.

Map

When you create an object literal in JavaScript the keys are always strings. If you write your object literal keys using non-strings the interpreter will either throw an error or convert them to strings depending on the data type. Here is an example of using numbers as keys and the interpreter converting them to strings:



 var item = {
    1:"Hair dryer",
    2:"Row machine",
    3:"Lamp",
    4:"Chair"
 }


 for (var prop in item) {

    console.log( "typeof " +  prop + " is " + typeof prop);
    //typeof 1 is string typeof 2 is string typeof 3 is string typeof 4 is string

 }

A map is like an object literal, but unlike object literals maps can have anydata type as a key.

To create a map you initialize the Map constructor.

let map = new Map();

To add or replace keys/value pairs you use the set method. To retrieve values, you use the get method.

"use strict";

let map = new Map();

map.set(1, "Hair dryer");// 1 is a key and "Hair dryer" is a value

console.log(map.get(1)); // Hair dryer

map.set(1,"Book");       // Replace value of key

console.log(map.get(1)); // Book

Like object literals, you can assign functions to keys.


"use strict";

let map = new Map();

map.set(1, function(){
    return "hello world"
});

console.log(map.get(1)()); // "hello world"

To initialize a map with multiple key/value pairs you must first place an array as an argument.


"use strict";

let map = new Map([]);

You now place arrays that have two values inside the initial array. The first value of each inner array is a key and the second is an assigned value.


"use strict";

let map = new Map([

    [1,"Hair Dryer"],
    [2,"Row machine"],
    [3,"Lamp"],
    [4,"Chair"]

]);

console.log(map.get(3)); // Lamp


You can determine the number of key/value pairs a map has by using the size property.


"use strict";

let map = new Map([

    [1,"Hair Dryer"],
    [2,"Row machine"],
    [3,"Lamp"],
    [4,"Chair"]

]);

console.log(map.size); // 4

To check if a map contains a key, you use the has method.

"use strict";

let user = new Map([

    ["name","Maureen"],
    ["age",40],
    ["profession","security guard"],

]);


user.has("age");  // true


To delete a key and its associated value you use the delete method.

"use strict";

let user = new Map([

    ["name","Maureen"],
    ["age",40],
    ["profession","security guard"],

]);


user.delete("name");

user.has("name"); // false

To remove all data from a map you use the clear method.



"use strict";

let user = new Map([

    ["name","Maureen"],
    ["age",40],
    ["profession","security guard"],

]);


user.clear();
console.log(user.size); // 0

Map keys must be unique. This means if you use the same key name more than once the previous key is overwritten.

"use strict";

let item1 = "test";  // same string
let item2 = "test";  // same string

let map = new Map();

map.set(item1,"stuff_1"); 
map.set(item2,"stuff_2");

console.log(map.size); // Size is 1 because item2 overwrites item1

If you use objects as keys, they are considered unique and you can do this:

"use strict";

let item1 = {};
let item2 = {};

let map = new Map();

map.set(item1,"stuff_1");
map.set(item2,"stuff_2");

console.log(map.size); // 2

The utillity of using objects as keys in a map is they allow you to associate additional data with the object without modifying it.

"use strict";

let map = new Map();
let maureen = {firstName: "Maureen", lastName: "Tanner"};
map.set(maureen, "This is my friend Maureen");
console.log(map.get(maureen)); // This is my friend Maureen

Iterating Maps

One problem with object literals that unlike arrays, there is no guarantee that data iteration will take place in any order.

ES6 maps do not have this problem. The data is accessible in order. To loop through a map, you use the forEach method. This method takes a callback that takes three arguments. The first argument is the value, the second is the key and the third is the entire map.



"use strict";


let map = new Map();

map.set("item-1","stuff-1");
map.set("item-2","stuff-2");
map.set("item-3","stuff-3");


map.forEach(function(value,key,map){
    console.log(value); 
    console.log(key);   
});

/*  

    item-1
    stuff-2
    item-2
    stuff-3
    item-3

*/

Weak Map

The main difference between a Map and a Weak Map is Weak Map only uses objects as keys, their data cannot be iterated through and they are more memory efficient that Maps. You create a Weak Map using the Weak Map constructor.

"use strict";

let map = new WeakMap();
let maureen = {firstName: "Maureen", lastName: "Tanner"};
map.set(maureen, "This is my friend Maureen");
console.log(map.get(maureen)); // This is my friend Maureen

Arrow Functions

Arrow functions are a syntactic replacement for anonymous functions. An arrow function replaces the function() syntax with ()=>.

An example without an arrow function

"use strict";

var add =  function(a,b){

    return a + b;
}

console.log(add(1,2));

An example with an arrow function

"use strict";

var add =  (a,b)=>{

    return a + b;
}

console.log(add(1,2));

Arrow functions are intended as a replacement for non-method functions such as callbacks. Here is an example of a callback function with and without arrow function syntax.

A callback example without an arrow function

"use strict";

var arr = [5,4,3,2,1];

arr.forEach(function(val){   // callback

    console.log(val);
    // Do more stuff    

});

A callback example with an arrow function

"use strict";

var arr = [5,4,3,2,1];

arr.forEach((val)=>{   // callback rewritten as an arrow function

    console.log(val);
    // Do more stuff    

});


Single Expression Arrow Functions

If your arrow function contains a single expression like the following example, it can be rewritten without curley braces or an explicit return statement.

"use strict";

var add =  (a,b)=>{

    return a + b;
}

console.log(add(1,2));


var add =  (a,b)=> a + b;    /* When written like this, the returned value is implicit */

console.log(add(1,2)); // 3

The Value of "this" in an Arrow Function

If you use an arrow function inside of another function the inner functions this keyword points to the same object as the harboring function. This is not the case when using non-arrow functions. To clarify, here is an example:

Non-arrow function example

var user = {
    name:"Bob",
    thisMethodPointsToUserObject:function(){
        
        var innerFunction = function(){

            console.log(this);   // If using "strict mode" "this" is undefined.
        }                        // If not using "strict mode" "this" points to
                                 // the window object
        innerFunction()
    }

}

user.thisMethodPointsToUserObject();

Arrow function example

var user = {
    name:"Bob",
    thisMethodPointsToObject:function(){

        var arrowFunc = ()=>{
            
            console.log(this); // The "this" of an arrow function points to the
        }                      // same object as its containing method

        arrowFunc()
    }

}

user.thisMethodPointsToObject(); 


The only other difference between conventional functions and arrow functions is that the "new" keyword cannot be applied to arrow functions.

The Class Keyword

In most object orientated programming languages a construct called a "class" is used to create objects. A class acts as a template or blueprint for an object. Technically JavaScript does not have classes but you can replicate their behavior by using Object.create(), factory functions or constructors. In ES6 yet another tool is available to replicate class behavior - a class keyword. Technically, JavaScript still behaves as a class-less language. The class keyword simply wraps JavaScript’s preexisting constructor functionality in a new syntax. The purpose of this is to make JavaScript look and "feel" like other object orientated languages. You do not need to use the class keyword to write good programs, but you do need to understand how it works.

You are now going to build a class to create "user" objects. Each user object has name and age properties. It also has a method named userData that retrieves the data assigned to the property values and returns a string that says: "name is age years old".

The first step is to name the class and create the body. Class names are capitalized.

class User {

}

To add properties, you create a function named constructor and add your properties like this:

class Student{

    constructor(name, age){

        this.name = name;   
        this.age = age;

    }

}

To add methods to your class you place them outside of the constructor function like this:

class Student{

    constructor(name, age){

        this.name = name;
        this.age = age;

    }

    //_______________________BEGIN method

    userData(){          
        return this.name +" is "+ this.age + " old"
    }

    //_______________________END method

}

To return a new object you invoke the class using the new keyword.

var lynda = new Student("Lynda",30);

console.log(lynda.name);       // Lynda
console.log(lynda.age);        // 30
console.log(lynda.getData());  // Lynda is 30 old

Extending Classes

In a previous module you learned that you can extend an object to include new properties and methods using Object.create(). When using ES6 classes you can extend a class. This allows you to contribute new methods and properties to a class without changing the original code.

The following example contains a class that represents a player in a video game. This class has two properties name and meters; and a method moveForward(). When the moveForward method is invoked the meters property is incremented with the total number of "meters" the player has traveled.

"use strict";

class Player {

    constructor(name){
        
        this.name = name;
        this.meters = 0;

    }

    moveForward(numberOfMeters){

        return this.meters += numberOfMeters
    }
}

let bob = new Player("Bob");

console.log(bob.moveForward(1)); // total number of meters is 1
console.log(bob.moveForward(9)); // total number of meters is 10

To contribute to this class without changing the original code you can extend it by creating a new class that inherits from the base class. The following example creates a class named Alien that extends the class named Player.The Alien class contains a new property named power and a method named flyUp.

"use strict";

class Player {

    constructor(name){
        
        this.name = name;
        this.meters = 0;

    }

    moveForward(numberOfMeters){

        return this.meters += numberOfMeters
    }
}

let bob = new Player("Bob");

console.log(bob.moveForward(1)); // total number of meters is 1
console.log(bob.moveForward(9)); // total number of meters is 10



class Alien extends Player{

    constructor(name,power){
        super(name);
        this.power = power;
    }

    flyUp(){

        return "You are now flying"
    }
}

let zod = new Alien("Zod","Zapper");

console.log(zod.moveForward(100)); // total number of meters is 100
console.log(zod.power);            // Zapper
console.log(zod.flyUp());          // you are now flying 
console.log(zod.moveForward(100)); // total number of meters is 200

Static Class Methods

To create a method that is invoked directly on a class and not from an object created from a class you use the static keyword.

"use strict";

class Player {

    constructor(name){
        
        this.name = name;
        this.meters = 0;

    }

    moveForward(numberOfMeters){

        return this.meters += numberOfMeters
    }

    static sayHello(){     // Static method
        return "Hello"
    }
}

console.log(Player.sayHello());  // Is invoked directly on Player. Returns Hello

let bob = new Player("Bob");

bob.sayHello() // Error. Is not available on returned objects

Promises

If you catch yourself writing code that requires a lot of asynchronous communication with the web browser (or other JavaScript environment such as Node.js ) and successive callbacks become difficult to reason about, then promises can help you to organize your code. The goal of promises is to remedy the "nested callback" problem.

This is what a nested collection of callbacks looks like:


getData(function(data) {
    doSomething1(function(data1) {
        doSomething2(function(data2) {
            doSomething3(function(data3) {
                doSomething4(function(data4) {

                });
            });
        });
    });
});

The following example is a "promise" version of the previous code.



getData()
  .then(doSomething1)
  .then(doSomething2)
  .then(doSomething3)
  .then(doSomething4)

Although you may consider the end result of using promises to being more syntactically pleasant, this does not mean writing programs that use promises is less work or that your code will be free of callbacks. Writing effective JavaScript requires the use of callbacks and promises are only used to solve the nesting problem.

We will explain promises in more technical detail momentarily, for now the goal is to teach you how to use promises as an alternative to nested callbacks.

Creating Your First Promise

A promise is created with the Promise constructor, therefore to create a promise requires the use of the new keyword. The promise takes a callback that takes two arguments: resolve and reject.

var myPromise = new Promise(function(resolve,reject){

})

The following code is the same as the preceding example and written using an arrow function:

var myPromise = new Promise((resolve,reject)=>{

})

When coding promises you want to look at them as a wrapper for any async function that you use to communicate with the web browser. Wrapping an async function in a promise is called "promisfying" it.

You will now write code to "promisfy" a setTimeout function.

Step 1. Determine the function you want to "promisfy".

    setTimeout(function(){     // We will promisfy the setTimeout function

    },1000)

Step 2. Create a function

let timer = function () {                       // Step 2. Create a function

}; 


Step 3. Return a promise from that function

let timer = function () {                     
    return new Promise((resolve, reject) => {   // Step 3. Return a promise
            

    });
}; 


Step 4. Wrap the code you want to "promisfy" in the promise you created in step 3.

To "promisfy" code you wrap it in the returned promise (in this case you are "promisfying" a setTimeout function but this could be an XMLHttpRequest or any other number of built-in async browser functions).


let timer = function (value) {
    return new Promise((resolve, reject) => {
            
        //___________________Step 5. wrapped async function

        setTimeout(() => {

            console.log(value);

        }, value);

       //_____________________END wrapped async function   

    });
}; 

Step 6. Set the resolve function.

The resolve() of a promise is like the return of a function. The resolve argument is a function that is invoked when the promise succesfully completes. The resolve() function takes an argument that is the value returned from the promise. In this case, the value placed as the argument of resolve() is the millisecond value passed to the setTimeout.

let timer = function (value) {  //__passed in argument named value
    return new Promise((resolve, reject) => {
            
        setTimeout(() => {

            console.log(value);

            
            resolve(value);   //____This is the returned value of a promise

        }, value);            //____value argument is millisecond value of setTimeout
            
    });
}; 

You then invoke your promise.

timer(1000);

Every promise has a then method and takes a callback that executes code after the previous async operation completes.

timer(1000)                  // after timer completes
    .then(function(){
        return timer(1000); //  run this code
    });

Promisfied functions can be chained using the then method.

timer(1000)
    .then(function(){
        return timer(1000)
    })
    .then(function(){
        return timer(1000)
    })
    .then(function(){
        return timer(1000)
    })

The preceding code can be refactored with arrow functions to make it less verbose.

 timer(1000)
    .then(()=>timer(1000))
    .then(()=>timer(1000))
    .then(()=>timer(1000))

If you were to write the previous code without a promise it would look something like this:


    setTimeout(() => {
             // do something
        setTimeout(() => {
             // do something
            setTimeout(() => {
             // do something
            }, 1000);
        }, 1000);
    }, 1000);

 

Promises in Detail

A promise is always in one of three states: pending, resolved or rejected. When a promise is pending it means that it is in the process of making an asynchronous request. If a promise has resolved it means it has completed a successful request. This gives you access to the then method. If a promise is rejected it means the request failed, and this gives you access to a method named catch (demonstrated below).

Resolve

To resolve a promise the resolve function must be invoked. A resolved function takes an argument that is the data returned from the promise. This data is accessible as an argument of the then method. Here is an example:

"use strict";

function doStuff(){

    return new Promise((resolve,reject)=>{

            // do some stuff
            resolve("some data") // To resolve the promise, this function must be invoked
    })
}

doStuff().then((data)=>{ // returned resolve function data is passed here   
    console.log(data);    // some data
});

Reject

The reject method of a promise is used to return an error when the "promisfied" code fails. If a completed promise does not resolve then it rejects. The rejection of a promise gives you access to a method named catch and lets you run code based on the promise rejection via a callback. Here is an example:


"use strict";

function doStuff(){

    return new Promise((resolve,reject)=>{

            // no resolve method
            reject("rejected!")
    });
}

doStuff().catch((error)=>{
    console.log(error);    // rejected!
});

The following example code combines the resolve and reject methods together into one example. Here, a function returns a promise that wraps a setTimeout function. The setTimeout will only run if the time argument is less than 3000 milliseconds (3 seconds). If the time value is greater than 3000 milliseconds then the reject method will be invoked and the catch method will give you access to the returned error.

function timer(time,message){

    return new Promise((resolve,reject)=>{

            if(time <= 3000){        // If time is less than 3 seconds

                setTimeout(()=>{
                    resolve(message) // Run code
                },time)

            }else{                   // Else return error message

                reject(new Error("Time needs to be less than 3 seconds"))
            }
    })
}

var value = 5000;

timer(value,"Timer up").then((message)=>{
    console.log(message);
}).catch((error)=>{
    console.log(error);
});


Symbols

ES6 adds a new data type to JavaScript - Symbol. A symbol only evaluates to true when compared to itself. To create a symbol you use the function Symbol(). The symbol function takes a single argument and is a descriptive string. The syntax looks like this:

"use strict";

let data = Symbol("some data");

console.log(data);                 // Symbol(some data)

console.log(data === data);        // true

console.log( Symbol("some data") === Symbol("some data") ); // false           

console.log(data === "some data"); // false
 

What Are Symbols Used For ?

They were added to the language in order to solve the problem of extending the functionality of Object while maintaining backwards-compatibility with code written in earlier versions of JavaScript. - -source

[Symbols] allow properties to be added to existing objects with no possibility of interference with the existing object properties, with no unintended visibility, and with no other uncoordinated additions by other code. - -msdn

Their only use is to avoid name clashes between properties - source

In other words, you use symbols to create object property keys and their purpose is to protect object properties from being overwritten or "clashing".

Using Symbols to Create Property Keys

"use strict";

var user = {

    id:"1111111",
    name:"Bob",
    age:30,
                  
}

var id = Symbol("symbol one"); // Create a Symbol

user[id] = "id 2222222";       // Make it a key and assign it some data   

var id = Symbol("symbol two"); // Create a new Symbol...What! It's the same variable name!

user[id] = "id 3333333";       // Make it a key and assign it some data  

console.log(user);

/* The object contains both symbols and original id property. No overwrites!


 {

     id: "1111111", 
     name: "Bob", 
     age: 30, 
     Symbol(symbol one): "id 2222222", 
     Symbol(symbol two): "id 3333333"

 }

*/

Accessing Unique Symbols

If an object has multiple property keys of the same name using a symbol then the way you can access it is using a method of Symbol named for. The for method allows you to place a Symbol in a global system registery.You can then retrive them using the same method.

var id = Symbol.for("my id");

var user = {
    name:"Bob",
    age:30,
    [id]:"my id 12345"     
}

var id = Symbol.for("my different id");

user[id] = "my different id 9876"

console.log(user[Symbol.for("my id")]);            // my id 12345
console.log(user[Symbol.for("my different id")]);  // my different id 9874

When you iterate through an object symbols are not accessible.

"use strict";

let id = Symbol("id");

var user = {
    name:"Bob",
    age:30,
    [id]:"8726348726432"
}


for(var prop in user){

    console.log(user[prop]);  // Bob 30 (no id)

}

The most common use for Symbols won't be making your own but rather using the built in "well-known symbols" like Symbol.iterator (introduced in in the iterables and iterators section below).

Like classes you do not need to use Symbols to write good programs but because they are a new data type, it is important that you have a working knowledge of them. For more information, we suggest you read this StackOverflow thread.

Iterators and Iterables

In the ES5 version of JavaScript you typically loop over a collection of data using a for-loop. ES6 has adopted an alternative approach that uses objects called iterators that programmatically return the next value in a collection. Unlike for-loops, iterators do not require the creation of an initialization variable to track collections. The purose of adopting iterators is to make iterating through data easier and more efficient. One of the reasons for adding Symbols to JavaScript is to implement this feature.

If a data source (such as array) has the property Symbol.iterator on its prototype it means it is designed according to a set of instructions called the iterable protocol and is referred to an an iterable.

The iterable protocol allows JavaScript objects to define or customize their iteration behavior - MDN

You can inspect the prototype of a data source for the Symbol.iterator property and see if it is iterable.

array console.log of symbol iterator property

Some collections are not iterable, such as object literals and the property Symbol.iterator is non-existant on their prototype.

object console.log

The for-of loop only works on iterable collections. Object literals are not iterable.

var numbObj = {
    zero:0,
    one:1,
    two:2
};

for (var prop of numbObj){
    console.log(prop); // ERROR!  Uncaught TypeError: numbObj[Symbol.iterator] is not a function
}

List of Iterables

The following data collections have the Symbol.iterator property on their prototype. The for-of loop is applicaple to all of these collections.

Arrays

"use strict";
var numbs = [0,1,2];           
for (var prop of numbs){
    console.log(prop); // 0 1 2    Works! This is because arrays are iterables!
}

Strings

"use strict";
for (let val of 'hello world') {
    console.log(val);     // h e l l o  w o r l d
}

Maps

"use strict";

let map = new Map();
map.set('item1', 1)
map.set('item2', 2);

for (let item of map) {
    console.log(item);  // [ 'item1', 1 ] [ 'item2', 2 ]
}

Sets

"use strict";

let set = new Set();
set.add('item-1');
set.add('item-2');
for (let item of set) {
    console.log(item);   // item-1  item-2
}

The arguments object The arguments object of a function is iterable.

"use strict";

function doStuff(a,b,c){

    for(var item of arguments){

        console.log(item);
    }
}

doStuff(1,2,3)  // console logs: 1 2 3

DOM node collections (in progress)

DOM node collections are being incorporated into the iteration protocol. In the current version of Chrome the following code works:

HTML

    <body>

    <div>item-1</div>
    <div>item-2</div>
    <div>item-3</div>

    <script src="js/app.js"></script>
    </body>

JavaScript



for(let node of document.querySelectorAll("div")){

    console.log(node);   /* <div>item-1</div> 
                            <div>item-2</div>
                            <div>item-3</div>
                         */
}

The Symbol.iterator property is a function. If you invoke the function it returns an iterator object.

You can run a next method on an iterator. This example demonstrates using an array.

"use strict";

let arr = [3,2,1];
let iterator = arr[Symbol.iterator]();
iterator.next();  // { value: 3, done: false }
iterator.next();  // { value: 2, done: false }
iterator.next();  // { value: 1, done: false }


The following example is the same as the previous one but it uses a DOM selector instead.

HTML

    <body>

    <div>item-1</div>
    <div>item-2</div>
    <div>item-3</div>

    <script src="js/app.js"></script>
    </body>

JavaScript

    "use strict"; 

    let node  = document.querySelectorAll("div")[Symbol.iterator]();
    console.log(node.next()); //Object {value: div, done: false}
    console.log(node.next()); //Object {value: div, done: false}
    console.log(node.next()); //Object {value: div, done: false}

The done property returns true when the iteration is complete.

"use strict";
let arr = [3,2,1];
let iterator = arr[Symbol.iterator]();
iterator.next();  // { value: 3, done: false }
iterator.next();  // { value: 2, done: false }
iterator.next();  // { value: 1, done: false }
iterator.next();  // { value: undefined, done: true }  done is true


Although the previous listed collections are considered iterable, there are many other JavaScript language constructs that make use of the iteration protocol including generators.

Generators

Generators are functions that have the abillity to pause their execution. You create a generator by placing an asterisk between the function keyword and the name of the function.

function *doStuff(){
    
}
"use strict";
function *doStuff(){

    yield 100;
    yield 200;
    yield 300;

}

let iterable = doStuff();

iterable.next(); // { value: 100, done: false }
iterable.next(); // { value: 200, done: false }
iterable.next(); // { value: 300, done: false }
iterable.next(); // { value: undefined, done: true } 

// NOTE: Generator functions have Symbol.iterator on their prototype

"use strict";
function *doStuff(){

}

console.log(doStuff.prototype);   

Generators have a return statement

// example

Generators can yield other generators

// example

Summary

Exercises

  1. "Promisfy" an XMLHttpRequest that makes a GET request to a URL. The URL is passed as an argument from the containing function. Use then to chain multiple calls to various web api's and log the returned data successively.

The end result will look similar to this:

AJAXRequest('https://itunes.apple.com/hk/rss/topalbums/limit=3/json')
.then(() => AJAXRequest ('https://api.github.com/users/bob'))
.then(() => AJAXRequest ('https://itunes.apple.com/hk/rss/topalbums/limit=10/json'))

Bonus Wrap a prompt method is a promise and use it to prompt for a github user name. Connect the promisfied prompt method to you AJAXRequest function and return the data for the Github user name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment