Skip to content

Latest commit

 

History

History
1017 lines (717 loc) · 23 KB

File metadata and controls

1017 lines (717 loc) · 23 KB

ES6 A.K.A ES2015

Recap & Intro

  • Last week we learned about application setup and configuration
  • We also discussed the use of loaders in your webpack config
  • Tonight we'll be discussing ES2015, the newest specification of JavaScript

Agenda

  • Tonight we'll cover the changes that ES2015 offers
  • The objective is for you become comfortable with the new features and syntax
  • We'll also cover how to include ES2015 in your current projects

What is ES2015

ES2015 (formerly known as ES6) is the biggest change to JavaScript to date. It offers many advantages over the current ES5 spec. While this may sound intimidating, ES6 is incredibly easy to pick up if you have a solid understanding of the core principles of JavaScript.

After learning a bit about ES2015 tonight, you'll be able to start using it immidiately.

Though ES2015 has officially been released, the features have yet to be fully implemented in current browsers, but have no fear, with a little bit of webpack magic you can write ES2015 code and have it compile/transpile into ES5.

Template Literals

What are template literals?

According to Mozilla:

Template literals are string literals allowing embedded expressions. You can use multi-line strings and string interpolation features with them. They were called "template strings" in prior editions of the ES2015 / ES6 specification.

  • Template strings are encolsed by the back-tick( ` ) character
  • instead of double(") or single(') quotes
console.log(`Hello! I'm a string template`) 
// Hello! I'm a string template
  • String templates also support multi-line strings with ease
the old-fashioned way
console.log("string text line 1\n"+
"string text line 2");
// "string text line 1
// string text line 2"
the ES2015 way
console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"

String Interpolation

String Literals also allow interpolation! That's right! no more concatenation of variables!

As you know (from experience) the only way to add variables to a string is by using concatenation with the + operator

the old-fashioned way
var name = "Shane";
var day = "Tuesday";

console.log("Hello "+ name + ", I hope you have a great " + day + "!");

While this works, ES2015 allows you to wrap your variables with ${}. This will take care of utlizing the value that your variable is pointing to.

the ES2015 way
var name = "Shane"; 
var day = "Tuesday";

console.log(`Hello ${name}, I hope you have a great ${day}!`)

As you can imagine this also works great with objects!

var day = "Tuesday";
var instructor = {
	name: "Shane",
	lesson: "ES 2015"
}

console.log(`Hello ${instructor.name}, I hope the ${instructor.lesson} class goes well on ${day}!`);

And if you're wondering about functions, that works too!

var instructor = {
	name: "Shane",
	lesson: "ES 2015",
	greet: function(){
	  return `Hi! I'm ${this.name} and tonight we'll be discussing ${this.lesson}!`;
	  }
}

console.log(instructor.greet());

Exercise

go to ES6Katas and pass the first 2 sets of tests

let & const

As you've been reading more about JavaScript you may have noticed the keywords let and const being used in code snippets. This new addition to the language is both simple and powerful.

let

In it's most basic form, let is a sibling of var. However there is a difference.

  • var creates a variable scoped to it's parent function (or in the global scope)
  • let scopes the variable to it's nearest block, (if statements, for loops, etc)

This concept is known as block scoping.

using var
function foo() {

  let x = true;
  if (x) {
    var usingVar = "I'm using the var keyword";
  }
  console.log( usingVar );
}
 
foo();
// I'm using the var keyword

In the above example, usingVar is hoisted to the top of the function, thus being made available throughout the function.

using let
function foo() {
  let x = true;
  if (x) {
    let usingLet = "I'm using the let keyword";
  }
  console.log( usingLet );
}
 
foo();
// usingLet is not defined

In the above example, usingLet is hoisted to the top of the if block, thus making it unavailable to the outer function's scope.

In summary, using let presents many benefits, including:

  • tighter control over your variables in regards to:
    • lexical scope
    • closures
    • hoisting
  • less errors at run-time
  • easier debugging

const

The const declaration creates a constant. Which is essentially a read-only reference to the value.

This means that once a constant is declared, it cannot be re-assigned or re-declared.

const instructors = ["Assaf", "Shane"]
instructors = ["Lee", "Mariel"]

// TypeError

A common misconception is that const is immutable. This is not entirely true. If the reference value of const is a complex object (ie: function, array, object), the contents can be modified.

const instructors = ["Assaf", "Shane"];
instructors.push("Lee", "Mariel");

console.log(instructors)
// ["Assaf","Shane","Lee","Mariel"]

A couple of other notes on const:

  • variables declared with const can be upper or lower-case
  • const follows the same block scope principles as let

The use of const and let gives you more control over your code and makes it much more readable for other developers.

Default Parameters

A welcome change in ES2015 is default paramaters.

This feature allows you to pass default arguments to your functions. Before we dive into the syntax, let's look at the ES5 work-around

the old-fashioned way
function hello(name) {
  name = name || 'Mystery Person';
 
  console.log('Hello ' + name + "!");
}

hello("Bobby");
// Hello Bobby!

hello();
// Hello Mystery Person!

While this is a very clever work-around, it can backfire when dealing with booleans and numbers.

ES2015 offers a much more reliable solution

the ES2015 way
function hello(name = "Mystery Person"){
	console.log(`Hello ${name}!`);
}

hello("Bobby");
// Hello Bobby!

hello();
// Hello Mystery Person!

Multiple default parameters can be added and mixed with regular parameters. However, keep in mind that parameters without default's will initially recieve the value of undefined (like normal).

function hello(day, name = "Mystery Person"){
	console.log(`Hello ${name}! have a great ${day}`);
}

hello();
// Hello Mystery Person! have a great undefined 

Exercise

  • Complete challenge #57 (Default paramaters) at ES6Katas

Rest & Spread

Rest

The rest parameter lets you pass an indefinite number of arguments as an array.

Rest parameters are invoked using the ... syntax

This is similar to the arguments object that we learned about way back in class #3. However, rest paramaters offer significant advantages.

the old fashioned way
function add() {
	console.log("arguments object:", arguments);
	
	var numbers = Array.prototype.slice.call(arguments),
	
	var sum = 0;
	
	numbers.forEach(function (number) {
	   sum += number;
	});
	return sum;
}

console.log(add(1,2,3,4,5,6,7,8));
// arguments object: {"0":1,"1":2,"2":3,"3":4,"4":5,"5":6,"6":7,"7":8}
// 36

In the above example, we had to convert the arguments object into an array, because it's not an actual array

Rest parameters on the other hand, provides an actual array
meaning methods like sort, map, forEach and pop can be applied on it directly.

the ES2015 way
function add(...numbers) {
  console.log("rest parameters:", numbers);

  let sum = 0;

  numbers.forEach(function (number) {
    sum += number;
  });
  return sum;
}

 console.log(add(1,2,3,4,5,6,7,8));
// rest parameters: [1,2,3,4,5,6,7,8]
// 36

As you can see, this new syntax is much more readable.
You can immediately tell this function can take any number of arguments.

Another rest params example
function addStuff(x, y, ...z) {
    return (x + y) * z.length
}

console.log(addStuff(1, 2, "hello", "world", true, 99));
// 12

Spread

The spread operator allows us to split and Array argument into individual elements.

let random = ["Hello", "world", true, 99];
let newArray = [1,2, ...random, 3];

console.log(newArray);
//[1,2,"Hello","world",true,99,3]

Another example, spreading out the characters in the hi variable

var hi = "Hello World"
var hiArray = [ ...hi ]

console.log(hiArray);
// ["H","e","l","l","o"," ","W","o","r","l","d"]

Rest vs Spread

Though the syntax is virtually the same, rest and spread serve different purposes...

  • rest
    • gathers values and stores them in an array
    • is used in function signatures (as an argument)
  • spread
    • spreads the values into individual array items
    • used in the function body
Rest Example
function restExample(...z) {
    console.log(z)
}

restExample("hello world")
//["hello world"]
Spread Example
function spreadExample(item) {
    let spreadArray = [...item]
    console.log(spreadArray);
}

spreadExample("Hello World");
// ["h","e","l","l","o"," ","w","o","r","l","d"]

Exercise

  • complete exercises 18 - 21 (Rest and Spread operators) at ES6Katas

Destructuring

Arrays

With destructuring you can store array items as individual variables during assignment.

Consider the following examples:

the old-fashioned way
var students = ["Julian", "AJ", "Matt"];
var x = students[0];
var y = students[1];
var z = students[2];

console.log(x, y, z);
// Julian AJ Matt
the ES2015 way
let students = ["Julian", "AJ", "Matt"];
let[x,y,z] = students

console.log(x,y,z);
// Julian AJ Matt

As you can see, this incredibly useful and readable. It also allows for scalability of your code.

Destructuring also allows you to omit values

let students = ["Julian", "AJ", "Matt"];
let[ ,y,z] = students

console.log(y,z);
// AJ Matt
let students = ["Julian", "AJ", "Matt"];
let[x,,y] = students

console.log(x,y);
// Julian Matt

Using destructuring with rest parameters

Now that you know how to use destructuring with and rest parameters, how would you implement both concepts

let students = ["Julian", "AJ", "Matt"];
let[x, ...rest] = students;

console.log(x, rest);
// Julian ["AJ","Matt"]

Array destructuring with functions

How would this work with functions?

function completedHomework(){
  return ["Julian", "AJ", "Matt"];
}

let [x,y,z] = completedHomework();
console.log(x,y,z);

Objects

Destructuring also works with objects

let instructor = {
  name: "Shane",
  email: "shane@techtalentsouth.com"
}
let { name: x , email: y } = instructor;

console.log(x);
// Shane

Bringing it all together

Let's use destructuring and default parameters in a function:

let instructor = {
  name: "Shane",
}

function teacher({name, email = "info@techtalentsouth.com"}) {
  console.log(name, email)
}

teacher(instructor);
// Shane info@techtalentsouth.com

Exercise

  • complete challenges 10-15 (Destructuring) at ES6Katas

Map, Set, WeakMap, WeakSet

Map is a new way to store key/value pairs, while similar to objects Map is a bit more reliable when storing key/values. This is due to the fact that Objects convert both keys and values to strings.

According to Mozilla

The Map object is a simple key/value map. Any value (both objects and primitive values) may be used as either a key or a value.

let student = {name: "Latori"};
let status = new Map();

status.set(name, "Latori");
status.set("feeling", "awesome")
console.log(status.get(name));
console.log(status.get("feeling"))
//Latori
//awesome
  • to set a value use set
  • to get an object use get

Set is a collection of unique values

let student = new Set();
student.add('Katy').add({mood: "happy"});

console.log(student);
// ["Katy",{"mood":"happy"}]

WeakMap works like Map, with a few small differences.

  • The keys must be objects
  • Allows for garbage collection of keys
  • does not allow iteration
let weakMap = new WeakMap();
let student = {}
weakMap.set(student,"Lee");

console.log(weakMap.get(student));
// Lee

WeakSet

Like WeakMap for Sets

let weakSet = new WeakSet();
let student = {
  name: "Heather"
};
weakSet.add(student);

console.log(student);
// {"name":"Heather"}

Arrow Functions

If you remember from lesson #5 when dealing with callbacks, you have to implement some work-arounds to keep the appropriate lexical scope.

As a refresher:

var teacher = {
    name: 'Shane',
    speak: function() {

        var boundFunction = function(){
            console.log('later my name is ' + this.name);
        }

        setTimeout(boundFunction,1000);
    }
}

teacher.speak();
// later my name is 

Obviously, this is not the intended result we're looking for. In ES5 there are several ways to produce the desired results.

the old-fashioned way
var teacher = {
    name: 'Shane',
    speak: function() {

        //Bind a function to a specific context
        var boundFunction = function(){
            console.log('later my name is ' + this.name);
        }.bind(this);

        //boundFunction will always run in bound context
        setTimeout(boundFunction,1000);
    }
}

teacher.speak();
// later may name is Shane

While this works, it feels like a hack.

Thankfully ES2015 solves the problem with arrow functions

the ES2015 way
var teacher = {
    name: 'Shane',
    speak() {
        let boundFunction = () => {
            console.log('later my name is ' + this.name);
        }

        setTimeout(boundFunction,1000);
    }
}

teacher.speak();
//later my name is Shane

Doesn't that feel better? the syntax involves:

  • removing the function keyword
  • adding () and any appropriate arguments
  • using the => operator
  • wrapping your function body in {}

This introduces the concept of lexical binding.
Which simply means: arrow functions bind to the scope of where they are defined not where they are used

with an argument
var students = [
  { name: "Edwin"}, 
  { name: "Kim"}, 
  { name: "Skip"}
  ];

var names = students.map((student) => {return student.name});

console.log(names);
// ["Edwin","Kim","Skip"]

Exercise

  • Make the tests pass for #5 and #6 (arrow functions) at ES6Katas

Classes

One of the most interesting/exciting features of ES2015 is the introduction of Object Oriented Keywords. The benefit of this feature, is that developers more accustomed to Object Oriented Programming can more easily work with Constructors and Prototypes.

note: the class features simply syntactic sugar, not an actual change to the functional nature of JavaScript

the old-fashioned way
function Person (name, job) {
  this.name = name;
  this.job = job;
};
 
Person.prototype.getName = function getName () {
  return this.name;
};
 
Person.prototype.getJob = function getJob () {
  return this.job;
};
var goodGuy = new Person('Jim Gordon', 'Commissioner');
console.log(goodGuy.getName());
// Jim Gordon
the ES2015 way
class Person {
 
  constructor (name, job) {
    this.name = name;
    this.job = job;
  }
 
  getName () {
    return this.name;
  }
 
  getJob () {
    return this.job;
  }
}

let goodGuy = new Person('Jim Gordon', 'Commissioner');
console.log(goodGuy);
//Jim Gordon

For those of you that have experience in Ruby or Python, this syntax should look and feel familiar.

  • Use the class keyword followed by a capitalized name
  • add a constructor function
  • add instance methods that give you access to the object's properties

Inheritance

This syntatic sugar also provides a really nice and clean way to create inheritance chains

note remember that JS inhertance is still instance based (not class based)

the old-fashioned way
function Person (name, job) {
  this.name = name;
  this.job = job;
};
 
Person.prototype.getName = function getName () {
  return this.name;
};
 
Person.prototype.getJob = function getJob () {
  return this.job;
};

function SuperHero (name, heroName) {
  Person.call(this, name, heroName);
}

SuperHero.prototype = Object.create(Person.prototype);
SuperHero.prototype.constructor = SuperHero;

SuperHero.parent = Person.prototype;
SuperHero.prototype.getJob = function () {
  return 'I am '+ this.job + "!"
};

var batman = new SuperHero('Bruce Wayne', 'Batman');

console.log(batman.getJob()); 

As you can see, this is pretty verbose. Let's take a look at the ES2015 way

the ES2015 way
class Person {
 
  constructor (name, job) {
    this.name = name;
    this.job = job;
  }
 
  getName () {
    return this.name;
  }
 
  getJob () {
    return this.job;
  }
}

class SuperHero extends Person {
 
  constructor (name, heroName, superPower) {
    super(name);
    this.heroName = heroName;
    this.superPower = superPower;
  }
  
  secretIdentity(){
    return `${this.heroName} is ${this.name}!!!`
  }
 
}
let batman = new SuperHero("Bruce Wayne", "Batman");

console.log(batman.secretIdentity())
// Batman is Bruce Wayne!!!

The 3 things that you'll notice:

  • create a new SuperHero class
  • use the extends keyword to indicate you want to inherit from the Person class
    after all, superhero's are People too
  • the use of super() allows us to:
    • reuse the exisiting name functionality from the Person class
    • add superhero specific features to our constructor function

Getters and Setters

If you have experience with Object Oriented programming, chances are you're familiar with getters and setters

Getters allow us to easily read (access) an object's property.
Setters allow us to write (modify) an object's property.

class Person {
 
  constructor (name) {
    this.name = name;
  } 
 
  set name (name) {
    this._name = name;
  }
 
  get name () {
    return this._name
  }
 
}

let goodGuy = new Person('Jim Gordon');
console.log(goodGuy.name);
// Jim Gordon

goodGuy.name = "James Gordon";
console.log(goodGuy.name);
// James Gordon

Other nifty features

  • ES2015 gives us a new way to easily add existing variables to objects.
let name = "Shane";
let job = "Developer";
    
let instructor = { name, job };

console.log(instructor);
// {"name":"Shane","job":"Developer"}

Modules

As you know, we've already talked about Modules quite a bit.
ES2015 offers a module syntax that encapsulates our code.

This provides several significant benefits including:

  • avoids naming conflicts
  • removes global variables
  • better control over scope
  • better control over 3rd party libraries
  • logical load order
  • faster tests

To many, this is the most exciting feature of ES2015

Modules consist of export and import statements

According to Mozilla:

Export:

...is used to export functions, objects or primitives from a given file (or module).

Import:

...is used to import functions, objects or primitives that have been exported from an external module, another script, etc.

let's take a look some basic examples:

exporting a single function
// ./src/calculator.js

export function add(...numbers) {	
  let sum = 0;
  numbers.forEach(function (number) {
    sum += number;
  });
  return sum;
}
// .src/index.js

import {add} from './calculator';

console.log(add(1,2,3));
exporting multiple items
// ./src/calculator.js

export function add(...numbers) {
  let sum = 0;
  numbers.forEach(function (number) {
    sum += number;
  });
  return sum;
};

export function subtract(x,y) {
  return x - y;
};
// .src/index.js

import {add, subtract} from './calculator';

console.log(add(1,2,3));
console.log(subtract(6,2));

Variables and Constants can also be exported

// ./src/calculator.js

export const numbersArray = [1,2,3,4,5];
// ./src/index.js

import _ from 'lodash';

console.log(_.shuffle(numbersArray));

Default Exporting allows you to set one item as default. This is helpful with a module or class that returns a single value

// ./src/calculator.js

export default function add(...numbers) {
  let sum = 0;
  numbers.forEach(function (number) {
    sum += number;
  });
  return sum;
};
// ./src/index.js

import add from './calculator';

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

Only one default can be clarified per module.
Modules can, however, have default and named exports

// ./src/calculator.js

export default function add(...numbers) {
  let sum = 0;
  numbers.forEach(function (number) {
    sum += number;
  });
  return sum;
};

export function subtract(x,y) {
  return x - y;
};
// ./src/index.js

import add, {subtract} from './calculator';

console.log(add(1,2,3));
console.log(subtract(6,2));

One final way to import is by using the * (all, wildcard) operator. This syntax will import all exports.

// ./src/calculator.js

export function add(...numbers) {
  let sum = 0;
  numbers.forEach(function (number) {
    sum += number;
  });
  return sum;
};

export function subtract(x,y) {
  return x - y;
};
// ./src/index.js

import * as calculate from './calculator';

console.log(calculate.add(1,2,3));
console.log(calculate.subtract(6,2));

As you can see, writing modular code is the future of JavaScript. The modular pattern will be heavily used as we move into building applications with React.

Exercise && Homework

  • complete the NodeSchool tower-of-babel module
  • complete this ES2015 tutorial
  • push the code to our class GitHub (name the project yourName_ES6)

Reading

YDKJS

For and Against Let

ES2015 Constants

Map, Set, WeakMap, WeakSet