Let's ES6!

Const & Let

Block scope vs.

Function scope

  • Remember that, in Javascript,  var declares a  function-scoped variable.
  • Const and let set a tighter scoping - "block" scoping
    • Rather than being hoisted to the top of a function's scope, variables declared with  const and  let are hoisted only to the top of the code block
    • Forif, while, try/catch all create code blocks
    • You can also create arbitrary code blocks, which can yield interesting use cases, particularly for garbage collection

Hoisting

function fooWithVar (bar) {
    if (bar) {
        var baz = 'baz'; 
    }
    console.log(baz);
}

// to the JavaScript interpreter:
function fooWithVar (bar) {
  var baz;
  if (bar) {
    baz = bar;
  }
  console.log(baz);
}

fooWithVar('hello world') // hello world
fooWithVar() // undefined

Blocked Out

function fooWithLet (bar) {
    if (bar) {
        let baz = 'baz'; 
    }
    console.log(baz);
}

// to the JavaScript interpreter:
function fooWithLet (bar) {
    if (bar) {
        let baz;
        baz = bar; 
    }
    console.log(baz);
}

fooWithLet(); // ReferenceError: baz is not defined


/** What's easier to debug: undefined, or a ReferenceError? */

Const

  • Declares a read-only reference to a value - all this means is that variables declared with "const" cannot be reassigned
  • You can still mutate the assigned content of the variable (ex. if you assign the variable to an object, you can still modify the object - you just can't assign that variable to something else, like another object)

Const Comment

'use strict';

const foo = [];
const bar = {};
const baz = 1;
const quux = 'hello';


foo.push(bar); // okay
bar.someMethod = function () {} // also okay

baz = 2 // TypeError: Assignment to constant variable
quux += ' world' // TypeError: Assignment to constant variable

"Letting" you in on a cool secret

// remember me from way back when?

function arrayOfFuncs(n) {
    let functions = [];

    /*  because using "let" to declare the variable "i" makes it block-scoped,
        you don't have to worry about it being closed over    */
    for (let i = 0; i < n; i++) {
        functions.push(function () {
            return i;
        });
    }
    return functions;
}

let myFunctions = arrayOfFuncs(5);
myFunctions[0]() // => 0    I work the way you thought I would!
myFunctions[1]() // => 1    Pretty cool, right?

Cleaning up

// the combination of let and arbitrary code blocks can help you free up memory faster

function suchCalculation () {
    /* very function */
}


/* Arbitrary code blocks are also new with ES6
   They prevent scope pollution - no need for a messy IIFE!
*/
{
    let muchData = { /* large dataset */ }
    suchCalculation(muchData);
}

/* At this point, the garbage collector know it can clean up the large data set we used, 
   since we're outside the code block
*/

console.log('wow!');

Arrow Functions

"This"

  • Remember that, within a function, this is defined based on the execution context of a function
    • If a function is executed as a method on an object,  this will refer to the object itself, so that you can intuitively access other properties/methods on the object

What if I told you...

// say we have an object called Neo...
const Neo = {};

// and we also have a function
const bendSpoon = function () {
    console.log(this.spoon);
};

// let's give our object a property and a method
Neo.spoon = 'spoon';
Neo.bendSpoon = bendSpoon;

// the execution context of bendSpoon is Neo, so this refers to Neo
Neo.bendSpoon(); // spoon

// the execution context of bendSpoon is the global context - there is no spoon
bendSpoon(); // undefined

Mr. Anderson...

const Neo = {};
const agentSmiths = ['Smith1', 'Smith2', 'Smith3'];

Neo.kick = function (agent) {
    console.log('Neo kicked ', agent);
};

Neo.kickAgentSmiths = function () {
    agentSmiths.forEach(function (agent) {
        this.kick(agent);
    });
};

Neo.kickAgentSmiths(); // TypeError: this.kick is not a function

// How can we help Neo?

Arrow functions

  • A more concise way to write function expressions
    • Reminder:
      • var fn = function () {}    function expression
      • function fn () {}              function declaration
  • The big difference: the body of arrow functions do not get their own dynamic  this value. Instead, arrow functions have lexical  this: their  this context is the enclosing context
    • This solves some issues you may have experienced with callback functions in methods like Array.prototype.forEach
    • However, with great power comes great responsibility....

Syntax

/*  if you have one argument and don't use brackets
    you can just write a snappy one-liner

    This is called the 'concise body'
*/
arr.map(i => i * 2);

//  multiple arguments go in parentheses
arr.reduce((prev, next) => prev + next);

//  no arguments are just an empty ()
//  note that you can use this for any function expression, not just callbacks!
let foo = () => console.log('bar');

/* if you need more than one line, then you need to use brackets
   and can't omit the return statement

    This is called the 'block body'
*/
let baz = (x) => {
    if (x > 1) return x;
    else return 1;
}

Using Arrow Functions

const Neo = {};
const agentSmiths = ['Smith1', 'Smith2', 'Smith3'];

Neo.kick = function (agent) {
    console.log('Neo kicked ', agent);
};

Neo.kickAgentSmiths = function () {
    agentSmiths.forEach(agent => this.kick(agent));
};

Neo.kickAgentSmiths();

/*
Neo kicked  Smith1
Neo kicked  Smith2
Neo kicked  Smith3
*/

Don't overdo it

const Neo = {};
const agentSmiths = ['Smith1', 'Smith2', 'Smith3'];

Neo.kick = function (agent) {
    console.log('Neo kicked ', agent);
};

Neo.kickAgentSmiths = () => {
    agentSmiths.forEach(agent => this.kick(agent));
};

Neo.kickAgentSmiths(); // TypeError: the Matrix has you

Arrow Combos

/**
*  Arrow functions love promise chains and array methods
*/

// Angular!
$http.get('/api/artists')
    .then(res => res.data)
    .then(artists => artists.filter(artist => artist.name === 'Kanye')
    .catch(err => console.error);


// Express!
router.get('/', (req, res, next) => {
  Artist
    .findAll({})
    .then(artists => res.json(artists)
    .catch(next);
});

Other Arrow Function Facts

  • Call and apply can only be used to pass arguments into a function expression defined using an arrow function - there's no affect on 'this'
  • There is no arguments object exposed by arrow functions - if you try to use arguments, it will just try to find an 'arguments' variable in the enclosing scope
  • If you want to use concise body to return an object literal, you need to wrap it in parentheses - otherwise, it looks like you're trying to use block body!

Classes

Background

  • Unlike many other languages (Java, Ruby, etc), JavaScript does not have class-based concepts built in
  • JavaScript uses prototypal inheritance, rather than class-based inheritance
  • ES6 introduces the class keyword, similar to other languages to make it more intuitive to "create" classes

Class Syntax

  • syntactic sugar for writing constructor functions that helps eliminate some boilerplate
  • familiar to developers coming from other languages
  • adding new functionality - behind the scenes, the behavior is the same as a constructor function
  • replacing prototypal inheritance somehow

What it is:

What it isn't:

Old dog...

'use strict';

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

// instance methods
Dog.prototype.bark = function () {
    console.log('arf!');
};
Dog.prototype.sayName = function () {
    console.log('Hi, my name is ', this.name); 
};

// static methods
Dog.fetchAll = function () {
    return $http.get('/api/dogs')
        .then(res => res.data)
    .catch(console.error);
};

const ben = new Dog('Ben', 'pitbull');
ben.bark(); // arf!

New tricks!

class Dog {
    constructor (name, breed) {
        this.name = name;
        this.breed = breed;
    }
	
    sayName () {
	console.log('Hi, my name is ', this.name);
    }
	
    bark () {
        console.log('arf!');
    }
	
    static fetchAll () {
        return $http.get('/api/dogs')
	    .then(res => res.data)
	    .catch(console.error);
    }
}

const ben = new Dog('Ben', 'pitbull');
ben.bark() // arf!

Inheritance

  • Inheritance, as we've seen, is sort of a pain in JavaScript
    • Using ParentClass.call in the constructor, Object.create, re-assigning ChildClass.prototype.constructor...
  • Using ES6 class syntax, all of that is gone!

Old pattern

'use strict';

function Animal (name) {
    this.name = name;
}
Animal.prototype.sayName = function () {
    console.log('Hi, my name is ', this.name);
};

function Dog (name, breed) {
    Animal.call(this, name);
    this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
    console.log('arf!');
};

"Super" inheritance

'use strict';

class Animal {
    constructor (name) {
        this.name = name;
    }
    
    sayName () {
	console.log('Hi, my name is ', this.name);
    }
}

class Dog extends Animal { // the 'extends' keyword extends the parent's prototype
    constructor (name, breed) {
        super(name); // the 'super' keyword replaces 'Animal.call(this, name)'
	this.breed = breed;
    }
	
    bark () {
	console.log('arf!');
    }
}

Still constructor functions!

'use strict';

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

// totally fine!
Dog.prototype.sayName = function () { /** etc */ }

console.log(typeof Dog) // "function"

See you around!

ES6 Juniors

By Tom Kelly

ES6 Juniors

  • 1,424