ES6 Basics

ES6 Basics

This post is not designed to be all digested at once. Take it a subject or two at a time. Use the Table of Contents to find what you need!

Table of Contents

Const and Let
Rest and Spread Operator
Default Parameters
Template Literals (Template Strings)
Object Shorthand
Destructuring
Import/Export
Arrow Functions

Much of ES6 is syntactic sugar.

"Syntactic sugar means that the new features of the language are not really new. Instead, they are a nicer syntax for something existing" - stackoverflow

This means that the code becomes more readable, and often times sheds unnecessary syntax. Pay attention to how the new key words and arrangements of characters would help clean up our code, help in the development process, or read more like English.

Const and Let

What is Const and Let?

We've historically defined variables using var

var name = "Bob Evans";

In ES6, we never need to use var again. It is going to be best practice to only use let and const.

const is used to declare variables that will never be re-assigned.

let is used to declare variables where the value could change at some point.

const name = "Bob Evans";
let address = "123 Fake Street";

Why use const and let?

In complex JavaScript programs, we will be using many variables. Because of these new variable declarations key words, let and const, we have more control over those variables.

Const and let also makes our code more readable. We can explicitly show what variables we expect to be altered, and which variables should stay the same.

For example, imagine we are writing a program to identify vowels. We would use const to declare the array of vowels because vowels will never change. They will always be: a, e, i, o and u.

const vowels = ["a", "e", "i", "o", "u"];
Scoping of Const and Let vs. Var

Another important thing to understand about const and let is that they are locally scoped whereas var is globally scoped.

For example:

function localScope(){
  if(2 === 2){
    let local = 'dog'
  }
  return local;
}
localScope()

//returns 'local' is undefined

but if it were the same function using var:

function localScope(){
  if(2 === 2){
    var local = 'dog'
  }
  return local;
}
localScope()

//returns 'dog'

Practice

change all the following vars to either const or let

var name = "Ben";
var age = 24;
var dateOfBirth = "feb";

var statuses = [ 
  { code: 'OK', response: 'Request successful' },
  { code: 'FAILED', response: 'There was an error with your request' },
  { code: 'PENDING', response: 'Your reqeust is still pending' }
];
var message = '';
var currentCode = 'OK';

for (var i = 0; i < statuses.length; i++) {
  if (statuses[i].code === currentCode) {
    message = statuses[i].response;
  }
}

Rest and Spread Operator

Rest Operator

Often times we want all our arguments to be an array of arguments. We don't know how many arguments will be passed into our function, but we want them to be an array in our function. We do that using the rest operator. ...whatWeWillNameOurArray

function addNumbers(...numbers) {
  // numbers is now an array that we can use .reduce() on
  return numbers.reduce((sum, number) => {
    return sum + number;
  }, 0);
}

addNumbers(1, 2, 3, 4, 5, 6, 7, 8);

Spread Operator

In this example, we will have two arrays that we want to be joined into one. Before, we would have had to use .concat().

const defaultColors = ['red', 'green'];
const userFavoriteColors = ['orange', 'yellow'];

defaultColors.concat(userFavoriteColors);

Now we can do this.

[ ...defaultColors, ...userFavoriteColors ];

The ES6 way is much easier to read and identify quickly was is being done.

Not only that, but now we can add more that two arrays.

[ ...defaultColors, ...userFavoriteColors, ...fallColors ];

We aren't done yet! We can also just add values!

[ "blue", "green", ...defaultColors, ...userFavoriteColors, ...fallColors ];

Let's write a shopping list validator that always includes milk. We will check for milk, and add it to our array if it's not there.

function validateShoppingList(...items) {
  if (items.indexOf('milk') < 0) {
    return [ 'milk', ...items ];
  }
 
  return items;
}

validateShoppingList('oranges', 'bread', 'eggs');

Practical example of Rest and Spread.

Let's assume we have a library with a depreciated method. We would like users of our library to use a better method, but don't what to break the code of people still using the old method.

const MathLibrary = {
  calculateProduct(...rest) {
    console.log('Please use the multiply method instead');
    return this.multiply(...rest);
  },
  multiply(a, b) {
    return a * b;
   }
};

We just pass on all of the arguments straight into the call of the new method. The arguments do get turned into an array, but then "spread" out and dumped into the new method and individual parameters.

Try to make the following function more ES6xy:

function product(a, b, c, d, e) {
  const numbers = [a,b,c,d,e];
  
  return numbers.reduce(function(acc, number) {
    return acc * number;
  }, 1)
}

Do this one too. Use at least both the rest and spread operators:

function unshift(array, a, b, c, d, e) {
  return [a, b, c, d, e].concat(array);
}

Default Parameters

Often times, we want to write a function with a default argument. For example, you have a function that returns the square inches of a piece of wood. Most of our boards are an inch think, but occasionally they are more. Thickness in this function would default to 1. To write this function in a way that sets thickness to 1, we used to need to say:

function calculateSquareInches(width, length, thickness){
    if (thickness === undefined){
        thickness = 1;
    }
    return width * length * thickness;
}

In this example, we have an entire if block to see if a value for thickness was passed through and to assign it to it's default if it wasn't.

But now we can write:

function calculateSquareInches(width, length, thickness = 1){
    return width * length * thickness;
}

It only assigns thickness to 1 if a third parameter wasn't passed through. Be sure to write all defaults as the last arguments when defining your functions.

Template Literals

These are designed for making our strings easier to read and write.

They utilize the "back tick." This symbol -> `. It's just left of the "1" on your keyboard. We put our entire string inside two back ticks, and we use a dollar sign and curly brackets to insert JavaScript variables and expressions into.

We used to concatenate strings like so:

let name = "Jacob";
let age = 20;
console.log("hello, my name is " + name + " and I am " + age + " years old.");

but now we can use the following syntax:

console.log(`hello, my name is ${name} and I am ${age} years old.`);

This is easier to read and write, we can easily identify where the JavaScript variables are, and it takes up less room. All those quotes and "+"s in the old way are way to easy to mess up. Start using the ES6 way!

You can also insert JavaScript expressions into the curly brackets.

console.log(`hello, my name is ${name}. Next year, I will be ${age + 1} years old`);

Object Shorthand

Object literal syntax is just a way to make our code look nicer. To show how to use this, we will just look at the old/uglier way of doing things, and then see the ES6/prettier way of doing things.

There are three different shorthand syntaxes for objects in ES6. ES6:

  1. Provides a shorthand syntax for initializing properties from variables.
  2. Enables the ability to have computed property names in an object literal definition.
  3. Provides a shorthand syntax for defining function methods.
const red = '#ff0000';
const blue = '#0000ff';

const COLORS = { red: red, blue: blue };

can now be

const red = '#ff0000';
const blue = '#0000ff';

const colors = { red, blue };

The old way:

const fields = ['firstName', 'lastName', 'phoneNumber'];
      
const props = { fields: fields };

Can now be:


const fields = ['firstName', 'lastName', 'phoneNumber'];
      
const props = { fields };

The old way:

const canvasDimensions = function(width, initialHeight) {
  const height = initialHeight * 9 / 16;
  return { 
    width: width, 
    height: height 
  };
}

Can now be:

const canvasDimensions = (width, initialHeight) => {
  const height = initialHeight * 9 / 16;
  return { 
    width, 
    height 
  };
}

Methods
Methods in an object no longer need to have the function keyword. To facilitate this, we drop the : too.

This

const color = 'red';

const car = {
  color: color,
  drive: function() {
    return 'Vroom!';
  },
  getColor: function() {
    return this.color;
  }
};

Can now be:

const color = 'red';

const car = {
  color,
  drive() {
    return 'Vroom!';
  },
  getColor() {
    return this.color;
  }
};

Destructuring

As we've found, the easiest way to learn the new ES6 syntax is to see the old way of writing JavaScript.

const expense = {
  type: "Business",
  amount: "$45 USD"
};

const type = expense.type;
const amount = expense.amount;

ugh. So much redundancy! We have const, type, and amount written three times. Using our old object, we can assign type and amount in a better way using curly-brackets.

const {type} = expense;
const {amount} = expense;

Variable names need to be identical to property names.

We can boil this down even a little further.

const { type, amount } = expense; 

We will often times see this syntax when importing specific parts of a file. Look forward to the next section to see examples of this.

Destructuring Arrays

we can make this code:

const vegetables = ["carrit", "tomat", "tomato soup"];

const firstVegetable = vegetables[0];

a lot prettier using square-brackets like this

const [ firstVegetable ] = vegetables

Notice the use of square-brackets vs the curly-brackets we were using before.

ES6 pulls the first item from the vegetables array, and assigns it to the firstVegetable variable. You can assign the first few like so:

const [ firstVegetable, secondVegetable ] = vegetables

Destructuring arrays and objects together

We are going to use two sets of destructuring in the last line of this snippet.

const people = [
  {name: 'Bob', age: 31},
  {name: 'Joe', age: 29},
  {name: 'Ben', age; 42}
];
const [{ name }] = people 

We start on the outside and go in, so name would equal Bob

Practical Examples

There are many times were we need to change our data structures. Destructuring is extremely useful for this.

Let's assume we have a grid of points:

const points = [
  [4, 5],
  [10, 1],
  [0, 40]
]

but we are working with a graphing library that wants an array structured like this:

[
  { x: 4, y: 5 },
  { x: 10, y: 1 },
  { x: 0, y: 40 }
]

To transform our data, we could use a for loop. But let's not risk looking like an ES6 noob. Let's go step by step. Each one of these steps would work, but we'll simplify our code bit by bit. In every step of the way, and this first example especially, try to use destructuring to simplify the code.

points.map(point => {
  const x = point[0];
  const y = point[1];
});
points.map(point => {
  const [ x, y ] = point;
});
points.map(([ x, y ]) => {
  return { x: x, y: y };
});

now, finally:

points.map(([ x, y ]) => {
  return { x, y };
});

One more example
Often we will use this to import specific parts of a package. All three following examples would do the exact same thing:

import { createStore } from "redux";
let { createStore } = require("redux");
let createStore = require("redux").createStore

Don't let import intimidate you, we'll talk about it later.

Practice

This takes a lot of practice. Try each of these examples on your own before looking at the solution.

Use destructuring to simplify this ES5 code:

const profile = {
  title: 'Engineer',
  department: 'Engineering'
};

function isEngineer(profile) {
  const title = profile.title;
  const department = profile.department;
  return title === 'Engineer' && department === 'Engineering';
}

answer in ES6:

const profile = {
  title: 'Engineer',
  department: 'Engineering'
};

const isEngineer = ({title, department}) => title === 'Engineer' && department === 'Engineering';

Import/Export

Before we would require npm packages or other files from our project. Now, we use the import syntax.

import axios from 'axios';

Is the same as saying

const axios = requre('axios');

We can also "unpack" only the methods or items that we need from the package or file.

import { Component } from 'react';

Export

Export is generally the same, except you can export multiple items. When this file gets imported, all these items will be packaged into the imported object.

You no longer say:

module.exports = codeOrFunctionOrObjectOrWhatever;

but instead say:

export codeOrFunctionOrObjectOrWhatever;

You can export however many items you'd like, and when they are imported, the import statement can specify which item it wants imported. You can also have an export default which will be exported if the import statement doesn't specify what it wants.

export default codeOrFunctionOrObjectOrWhatever;

Arrow Functions

Arrow functions simplify the syntax of our traditional functions.

They use symbols that make intuitive sense to help make our functions simpler.
The old way of writing functions:

const add = function(a, b){
    return a + b;
}

With arrow functions, we can remove the function key word, and add an arrow comprised of = and >.

const add = (a, b) => {
    return a + b;
}

Cool, our function is already simpler. This syntax makes sense. It's like saying "I'm going to take a and b and transform them." Right? And arrow suggests transportation, transformation, forwarding, moving, etc. "I'm going to take a and b and 'arrow' them."

But wait, there's more!!

We can simplify it some more. If your function has a single expression. Or a single line of code in the function, you can remove the curly brackets and return keyword

const add = (a, b) => a + b;

This is called an implicit return.

The rule is, if you have a just one expression in your function, remove both the curly brackets and the return keyword.

If you only have one argument, you can also drop the parentheses!

const double = (num) => num * 2;

can be written:

const double = num => num * 2;

If there are zero arguments, you need the parentheses.

const logHello = () => console.log("hello");

Arrow functions start to look especially sexy when used as anonymous callback functions.

const numbers = [1, 2, 3]

numbers.map(function(number) {
    return 2 * number;
});

can be:

const numbers = [1, 2, 3]

numbers.map(number => 2 * number);

This is possible because we are using arrow functions and .map() instead of a for loop.


Practice using these tools in your code. Get comfortable with reading and writing them.