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
- For, if, 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
-
Reminder:
- 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