ES6 of the Week

This Week's Episode:

symbols, iterators/iterables, and generators, oh my!

Symbols

  • a brand new primitive type!
Symbol([description])
var foo = new Symbol("bar")
var foo = Symbol("bar")

The key to my... object?

Uniqueness

Enumerability 

Unique symbol butterflies

const slimShady = Symbol('eminem'); // the string 'eminem' is just to help us
const theRealSlimShady = Symbol('eminem');

slimShady === theRealSlimShady // false
theRealSlimShady === theRealSlimShady // true - you have a direct reference 
//to the symbol so they point to the same symbol in memory



const fiftyCent = Symbol();  // Symbol is just a unique identifier

typeof fiftyCent === 'symbol'; // a new primitive for real!

let rappers = {};
rappers[fiftyCent] = "Curtis James Jackson III";

// attempting to get using dot notation doesn't work
rappers.fiftyCent // undefined

// you MUST pass in the Symbol reference using bracket notation
rappers[fiftyCent] // "Curtis James Jackson III"

Loop de loop

for... in...

for... of...

Object.keys()

Object.getOwnPropertyNames()

JSON.stringify()

const fooSym = Symbol('foo');
const myObj = {};
myObj['foo'] = 'bar';
myObj[fooSym] = 'baz';

Object.keys(myObj); // -> [ 'foo' ]
Object.getOwnPropertyNames(myObj); // -> [ 'foo' ]
Object.getOwnPropertySymbols(myObj); // -> [ 'foo', Symbol('foo') ]

Pt 2

iterators and iterables

Iterator protocol

  • can access items in a collection
  • can keep track of its current position 
  • has a next() method
    • returns an object with: 
      • value
      • done

Iterable protocol

  • defines its iteration behavior
  • must have a property with the Symbol.iterator key (either itself or through prototypical inheritance)

Using iterators

'use strict';

// an array is a built-in iterable
let numberArray = [1, 2, 3];
// Symbol.iterator is the special property that contains a function that returns an iterator
let it = numberArray[Symbol.iterator]();

// we can then use the iterator to manually iterate through the array's values
it.next(); // Object { value: 1, done: false }
it.next(); // Object { value: 2, done: false }
it.next(); // Object { value: 3, done: false }
it.next(); // // Object { value: undefined, done: true }

let stringArray = ['a', 'b', 'c'];

// the for...of loop consumes an iterable
for (let ch of stringArray) {
    console.log(ch) // a b c
}

DIY

let strArray = ['a', 'b', 'c'];
strArray[Symbol.iterator] = function () {
    let idx = 0,
        collection = this,
        len = this.length;
    
    return {
        next: function () {
            if (idx < len) {
                let value = collection[idx];
                idx++;
                return {
                    value: value + '!',
                    done: false
                };
            } else {
                return {
                    value: undefined,
                    done: true
                };
            }
        }
    };
};

for (let ch of strArray) {
    console.log(ch); // a! b! c!
}

Pt 3

generators

Generators

  • new type of function that can maintain its own state
  • always return an object which adheres to the Iterator Protocol (an iterator)
  • uses the yield keyword 

Generation station

// we declare a generator using an asterisk
function* myGenerator (n) {
    // we yield values with the yield keyword
    yield n;
    n++;
    // we can yield as many times as we want
    // each yield is a "pause" in the generator function's execution
    yield n;
}

/* 
invoking a generator function returns an iterator!
we've passed in a value for the parameter "n", but
no other code in the generator has executed yet
*/
let myIterator = myGenerator(41);

// each call to next advances us to the next yield
let firstYield = myIterator.next();
console.log(firstYield.value); // 41
let secondYield = myIterator.next();
console.log(secondYield.value); //42

Call and Answer

// because we can pause generators, it doesn't matter if they run forever
function* generateDoubles (initialValue) {
    let x = initialValue;
    while (true) {
    	x = yield x * 2;
    }
    /*
    Something really cool is happening here!
    We can yield on the right side of an assignment.
    When we call .next on the iterator, we can pass in a value as a parameter.
    That value will then be the value that gets assigned!
    */
}

let i = 1,
    it = generateDoubles(1), // invoking the generator 'primes' it with the parameter value
    two = it.next().value, // this first call to .next advances us to the yield
    four = it.next(two).value, // each subsequent call picks up where the last yield left off!
    eight = it.next(four).value;

/*
  Each yield pauses the function and yields a value.
  If the yield is in an assignment, it will also wait
  to receive a value from outside as well!
*/

ES6 of the Week - 5 (symbols, iterators, iterables, and generators)

By beelai88

ES6 of the Week - 5 (symbols, iterators, iterables, and generators)

Symbols, Iterators/iterables, generators

  • 613