Skip to content

Instantly share code, notes, and snippets.

@BritneyJo
Last active February 11, 2021 07:02
Show Gist options
  • Save BritneyJo/e719ab8e746382df5a9c9b2fff7879e9 to your computer and use it in GitHub Desktop.
Save BritneyJo/e719ab8e746382df5a9c9b2fff7879e9 to your computer and use it in GitHub Desktop.

ES6 / ES2015

Learning Objectives

  • Understand what ES2015 is and how to use it
  • Exposure to and implementation of many of the most useful new features introduced in ES2015:
    • String Interpolation
    • Arrows
    • Let vs. Const vs. Var
    • Classes
    • Object Literals
    • Default Parameters
    • Spread Operator
    • Destructing
    • Promises

ES2015 Background

ECMAScript 6, also known as ECMAScript 2015, is the latest version of the ECMAScript standard that was released in June 2015. ES6 is a significant update to the language, and the first update to the language since ES5 was standardized in 2009. Implementation of these features in major JavaScript engines is underway now. It adds a ton of new syntax and functionality aimed at making writing complex applications easier.

Where / How can I use its features?

Cool! Can I use it? The short answer is yes! (cross your fingers).
Check out this awesome table of features and where they are supported.

TL;DR: Decent desktop support of Firefox, Edge (MS finally ditched IE and made something awesome), Chrome (with 'use strict'). Somethings work on Node, Safari is still 'meh' and there is limited ES6 support on mobile browsers.

The current solution to improve compatibility is to transpile your ES6 code to ES5 using something like Babel or Traceur. I use Babel because at the moment it has slightly better feature support and an awesome REPL.

We can also check out code using this ES6 Fiddle.

ECMAScript 6 Features

Template Strings

Template strings provide syntactic sugar for constructing strings. This is similar to string interpolation features in Perl, Python and more.

Multi-line Strings

// ES5
"<h1>❤ unicorns</h1>\n" +
"<p>Unicorn pegasus pony rainbows pegasus pony kittens. Pop pigeon rainbows pony delight kittens kittens surprise. Wereunicorn delight pony pony social unicorn surprise.</p>\n" +
"<ol>\n" +
"<li>Yea! Yeah!</li>\n" +
"<li>Yeah, woo-hoo!</li>\n" +
"</ol>"

In ES2015 you can place strings between back-ticks (the symbol below the esc key) to use multi-line strings:

// ES2015
`<h1>❤ unicorns</h1>
<p>Unicorn pegasus pony rainbows pegasus pony kittens. Pop pigeon rainbows pony delight kittens kittens surprise. Wereunicorn delight pony pony social unicorn surprise.</p>
<ol>
<li>Yea! Yeah!</li>
<li>Yeah, woo-hoo!</li>
</ol>`

Template Literals or String Interpolation

// ES5
var name = "Sam", age = 71;
"Hello my name is "+ name +" and I'm "+ age +" years old!"

ES2015 introduces template literals which make the process much less painful. Back-ticks are also required and this also supports multi-line strings.

// ES2015
var name = "bob", age = 71;
`Hellow my name is ${name} and I'm ${age} years old!`

YOU DO Fix the following tests: http://tddbin.com/#?kata=es6/language/template-strings/basics

New String Methods

With ES6, the standard library has grown immensely. Along with these changes are new methods which can be used on strings, such as .includes() and .repeat().

//ES5
var string = 'food';
var substring = 'foo';

console.log(string.indexOf(substring) > -1);

//ES6
const string = 'food';
const substring = 'foo';

console.log(string.includes(substring));

//ES5
function repeat(string, count) {
    var strings = [];
    while(strings.length < count) {
        strings.push(string);
    }
    return strings.join('');
}

//ES6
// String.repeat(numberOfRepetitions)
'meow'.repeat(3); // 'meowmeowmeow'

Let and Const

Let

let is the new var.

The var statement declares a variable globally, or locally to an entire function, optionally initializing it to a value.

// ES5
function varTest() {
  var x = 31;
  if (true) {
    var x = 71;  // same variable!
    console.log(x);  // 71
  }
  console.log(x);  // 71
}

The let statement declares a block scope local variable, optionally initializing it to a value.

// ES2015
function letTest() {
  let x = 31;
  if (true) {
    let x = 71;  // different variable
    console.log(x);  // 71
  }
  console.log(x);  // 31
}

Another Example:

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // same variable!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // different variable
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

varTest();
letTest();

Const

Everything let does const also does, except that const can't be reassigned. The const declaration creates a read-only reference to a value. It does not mean the value it holds is immutable. Const requires an initial value. It is read-only.

const MY_FAV = 7;

// this will throw an error in Firefox and Chrome (but does not fail in Safari)
MY_FAV = 20;

// will print 7
console.log("my favorite number is: " + MY_FAV);

Concise Object Methods / Object literals

'Concise Object Methods' are a new syntax for writing functions with objects that allow you to skip writing ': function'

Remember an object literal is a list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}).

// ES5
var doStuff = {
  sayHello: function() {
    console.log("Hello");
  },
  eatPizza: function(pizza) {
    pizza = 0;
    return "thanks!"
  }
}
// ES2015
let doStuff = {
  sayHello() {
    console.log("Hello");
  },
  eatPizza(pizza) {
    pizza = 0;
    return "thanks!"
  }
}

Arrows

Arrows are a function shorthand using the => syntax. The two biggest motivations for arrows were shorter functions and lexical this. Arrows can make your code cleaner and more intuitive. They are syntactically similar to the related feature in C#, Java 8 and CoffeeScript. They support both statement block bodies as well as expression bodies which return the value of the expression.

Fat Arrows for Functions

You do!

  1. Go to the Babel REPL.
  2. Paste in the code below:
//ES5
var add = function(x, y){
	return x+y;
}

//ES6
var add = (x , y) => x + y;

What did you notice? Compare the syntax between ES6 (left) and ES5 (right).

Arrows save having to write function & return for simple functions like this. When only one variable is used, the parenthesis can also be omitted x => x * x, I like to keep the parens for consistency.

Arrows can also be used with block statements. It should be noted that in blocks the return is not automatic and needs to be explicitly stated. This still saves having to write function.

YOU DO Paste the code below into the Babel REPL.

$('#pizza-btn').click(function (event) {
  preheatOven();
  pizzaInOven();
  return 'I love za!';
});

// ES6
$('#pizza-btn').click( (event) => {
  preheatOven();
  pizzaInOven();
  return 'I love za!'
});

Shorter syntax is awesome, especially because I'm always misspelling funciton.

Best Practice is to use arrow functions in place of function expressions when possible.

Fat Arrows for this

Where arrows really shine is by binding this. The this in an arrow is set to the this value of the enclosing execution context.

Context refers to the object that the currently executing function is attached to - determined in most cases by how a function is called. The value of this references whatever the current context is. Scope is where a variable can be referenced (where it's defined).

Here we have a function, in this case an Object constructor function. Inside our function is a method that executes a callback function. We want the ovenTemp of the Oven to increase every 1000 ms.

function Oven () {
  this.ovenTemp = 0; // here `this` references instance of Oven constructor

  setInterval(function preheatOven() {
    // preheatOven() redefines context of `this`
    this.ovenTemp++;  // here `this` references the global object, undesired action
  }, 1000);
}

What does this refer to in both spots?

  • The Oven.ovenTemp doesn't change because our callback changed the context of this.

How can we fix the problem?

  • One way we can remedy the problem by binding this.
function Oven () {
  this.ovenTemp = 0; // here `this` references instance of Oven constructor

  setInterval(function preheatOven() {
    this.ovenTemp++;  
  }, 1000).bind(this);
}

Another, more performant, way to fix the problem is to store the top level this reference/context in a variable, commonly seen as self = this or that = this.

function Oven () {
  var self = this; // stores top level `this` reference
  self.ovenTemp = 0;

  setInterval(function preheatOven() {
    self.ovenTemp++; // performs desired action
  }, 1000);
}

A better solution is to leverage the lexical scoping of arrow functions.

function Oven () {
  this.ovenTemp = 0;

  setInterval( preheatOven = () => {
    this.ovenTemp++;  
    // here "this" references instance of Oven constructor, as desired
  }, 1000);
}

In this particular example we can use an anonymous callback and refactor to one line. Wow that's pretty!

function Oven () {
  this.ovenTemp = 0;
  setInterval( () => { this.ovenTemp++; }, 1000);
}

This trivial example shows a non trivial use case - arrows can simplify callbacks and/or closures and allow for more intuitive use of context.

Best Practice is to use arrow functions whenever you need to preserve the lexical value of this.

Default Parameters

In JavaScript, parameters of functions default to undefined. However, in some situations it might be useful to set a different default value. This is where default parameters can help. Default function parameters allow formal parameters to be initialized with default values if no value or undefined is passed.

//ES5
function addTwoNumbers(x, y) {
    x = x || 0;
    y = y || 0;
    return x + y;
}

// ES6
function addTwoNumbers(x=0, y=0) {
    return x + y;
}

addTwoNumbers(2, 4); // 6
addTwoNumbers(2); // 2
addTwoNumbers(); // 0


function append(value, array = []) {
  array.push(value);
  return array;
}

append(1); //[1]

Spread

The spread operator ... allows an expression to be expanded into multiple elements.

What does it do?

From MDN:

In ES6, spread syntax allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals).

Simply put, the spread syntax can be used to access all of the elements of an iterable data structure.

var foo = [4, 6];

var bar = [1, ...foo, 2, 5];     // [1, 4, 6, 2, 5]

or

function myFunction(a, b, c) {
  return a + b + c;
}

var foo = [1, 2, 4];

myFunction(...foo)   // 6

This is useful for separating an array into individual elements:

var dimensions = [10, 5, 2];
var volume = function(height, width, length){
  return height * width * length;
}
volume(...dimensions);

// versus

volume(dimensions[0], dimensions[1], dimensions[2])

This also makes it very easy to create copies of an array in functions where mutation occurs:

var days = ["monday","tuesday","wednesday","thursday","friday","saturday","sunday"]
function reversedDays(arr){
  return arr.reverse()
}
console.log(reversedDays(days))
// but now days is no longer in order
console.log(days)

// To deal with this, we can either:

function reversedDays(arr){
  var newArray = []
  for(let i = 0; i < arr.length; i++){
    newArray.push(arr[i])
  }
  return newArray.reverse()
}
console.log(reversedDays(days))
console.log(days)

// or... (<- pun)

function reversedDays(arr){
  return [...arr].reverse()
}
console.log(reversedDays(days))
console.log(days)

Why use it?

List concatenation is now simple and intuitive:

A good use case is a function that can take any number of arguments, e.g., Math.max()

var arr = [1, 3, 15, 8, 9, 6];

Math.max(1, 3, 15, 8, 9, 6);    // 15

Math.max(arr);                  // NaN

Math.max(...arr);               // 15

You do: Spread Practice

  1. https://github.com/ga-wdi-exercises/es6-exercises/blob/master/03-spread-practice.js

Classes

Prior to ES6, we implemented Classes by creating a constructor function and adding properties by extending the prototype:

// ES5
function Pizza(name, temperature) {
  this.name = name;
  this.temperature = temperature
}

Pizza.prototype.heatUp = function () {
  return this.temperature + 20;
};

Classes provided syntactic sugar over JavaScript's existing prototype-based inheritance. A class body can only have methods and not data properties. Putting data in prototypes is generally an anti-pattern, so this enforces a best practice. Data is instead attached to classes using the constructor method. Instance methods (defined with concise object syntax) are automatically connected through prototypical inheritance.

// ES6
class Pizza {
  constructor(name, temperature) {
    this.name = name;
    this.temperature = temperature;
  }

  heatUp() {
    return this.temperature + 20;
  }

  static sayCool() {
  	console.log("cool!");
  }
}

The static keyword defines a static method for a class. Static methods are called without instantiating their class and are also not callable when the class is instantiated. Static methods are often used to create utility functions for an application. MDN static

// ES2015
Pizza.sayCool();
// > "cool!"
let za = new Pizza("cheese", "hot")

za.sayCool();
// TypeError: za.sayCool is not a function

Destructing

Destructing is a new syntax that makes it easier to extract data from objects and arrays (even deeply nested) and store them in variables with a more convenient syntax.

//ES5
var arr = [1, 2, 3, 4];
var a = arr[0];
var b = arr[1];
var c = arr[2];
var d = arr[3];

//ES6
let [a, b, c, d] = [1, 2, 3, 4];

console.log(a); // 1
console.log(b); // 2

// ES5
var luke = { occupation: 'jedi', father: 'anakin' };
var occupation = luke.occupation; // 'jedi'
var father = luke.father; // 'anakin'

// ES6
let luke = { occupation: 'jedi', father: 'anakin' };
let {occupation, father} = luke;

console.log(occupation); // 'jedi'
console.log(father); // 'anakin'

For destructing objects the assigned name needs to match the keys of the object

// ES2015
let x = {y: "pizza", z: 100}
let {foo, bar} = x // not useful: neither foo or bar are in 'x'

console.log(foo); // undefined
console.log(bar); // undefined

Promises

Promises allow us to turn our horizontal code (callback hell):

func1(function (value1) {
    func2(value1, function (value2) {
        func3(value2, function (value3) {
            func4(value3, function (value4) {
                func5(value4, function (value5) {
                    // Do something with value 5
                });
            });
        });
    });
});

Into vertical code:

func1(value1)
    .then(func2)
    .then(func3)
    .then(func4)
    .then(func5, value5 => {
        // Do something with value 5
    });

Prior to ES6, we used bluebird or Q. Now we have Promises natively:

new Promise((resolve, reject) =>
    reject(new Error('Failed to fulfill Promise')))
        .catch(reason => console.log(reason));

Where we have two handlers, resolve (a function called when the Promise is fulfilled) and reject (a function called when the Promise is rejected).

Benefits of Promises: Error Handling using a bunch of nested callbacks can get chaotic. Using Promises, we have a clear path to bubbling errors up and handling them appropriately. Moreover, the value of a Promise after it has been resolved/rejected is immutable - it will never change.

Here is a practical example of using Promises:

var request = require('request');

return new Promise((resolve, reject) => {
  request.get(url, (error, response, body) => {
    if (body) {
        resolve(JSON.parse(body));
      } else {
        resolve({});
      }
  });
});

We can also parallelize Promises to handle an array of asynchronous operations by using Promise.all():

let urls = [
  '/api/commits',
  '/api/issues/opened',
  '/api/issues/assigned',
  '/api/issues/completed',
  '/api/issues/comments',
  '/api/pullrequests'
];

let promises = urls.map((url) => {
  return new Promise((resolve, reject) => {
    $.ajax({ url: url })
      .done((data) => {
        resolve(data);
      });
  });
});

Promise.all(promises)
  .then((results) => {
    // Do something with results of all our promises
 });

Learn more

  • nested object and array destructing
  • combine with default parameters

MDN Destructing

Testing ES6 Lab

http://es6katas.org/

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