Let’s Learn ES2015

Avatar of Ryan Christiani
Ryan Christiani on (Updated on )

The following is a guest post by Ryan Christiani. Ryan is a teacher at HackerYou and has been working on a video series called Let’s Learn ES6. He offered to put some of that together into a tutorial format, which I thought was perfect as we haven’t covered ES2015 much here on CSS-Tricks.

What’s in a name?

As of June 2015, the largest addition to the JavaScript language was finalized. The official name is ECMAScript 2015, sometimes referred to as “ES6”, or now more commonly known as “ES2105”. It is the culmination of years of work and features.

Moving forward, there will be ECMAScript 2016, which will likely be referred to as “ES7” or “ES2016”. The plan is to have incremental yearly releases.

Most browsers have started to implement the ES2015 features, but support varies between them. You can see the current browser compatibility for this implementation using this table.

Tools like Babel allow us to write new ES2015 code today and perform a task called transpiling (much like preprocessing) to convert the code into a earlier version of JavaScript that has greater browser support. This is similar to how Sass works; initially writing your code in Sass syntax, and then a preprocessor compiles to standard CSS.

Overview

In this article we will look at a few features that are now available to use as developers.

We will look at new keywords like let and const, how to create template literals to make concatenation easier, the new arrow function syntax, spread operator and rest parameters! Here’s a table of contents:

  1. let and const
  2. Template Literals
  3. Arrow Functions
  4. Spread Operators
  5. Rest Parameters

These additions can help make writing JavaScript a real joy!

let and const

let and const are two new keywords that are now available in ES2015. They are used to declare variables, however there is one key feature these variables share that sets them apart from var: they create block scoped variables.

When you use the var keyword to create a variable it is function scoped, and it is local only to that function. This means it is available within the function it was created in and any function nested inside of that one. But it is NOT available outside of there. If you used var to define a variable outside of any function it would be available globally.

One common issue we will run into with function scoped variables is the for loop.

for (var i = 0; i < 10; i++) {
  console.log(i);
}
console.log(i); // Will print out 10;

It is common to declare a variable inside of the for loop with the intent of it being bound to just that for loop however that is not that case. If you run the above code you will see the i variable is available outside of the for loop.

If you want to use let or const you will have to enable strict mode for your JavaScript file first. By adding 'use strict' at the top of your document you enable a restricted variant of JavaScript.

'use strict';

Strict mode is a way to opt into a version of JavaScript that fixes some mistakes in the language, turning them into errors. It also prohibits syntax that will likely be defined in the future! For example, in strict mode, you cannot make a variable with the name of let. For more information on strict mode, check out the MDN page on the topic.

(Editor’s note: if we’re using Babel then we don’t have to worry about “use strict” because it will automatically add that to our code, but it’s certainly worth knowing that it happens.)

A “block” in JavaScript is anything between { }. So when we talk about block scope, that means that any variables defined in curly brackets will only exist in that block!

var is function scoped, so creating a variable inside of a block with var will make it available outside of the block as well.

{
  var user = "Ryan";
}
console.log(user); // Ryan

When you define a variable with the let keyword it will create a new variable only within the { } or block.

{
  let user = "Ryan";
}
console.log(user); // Uncaught ReferenceError: user is not defined

This defines and binds a variable only to the block in which it is in! If we take look at the for loop example again, and replace var with let

for (let i = 0; i < 10; i++) {
  console.log(i);
}
console.log(i); // Uncaught ReferenceError: i is not defined 

Now it works as intended. The const keyword behaves the exact same way, with one exception. Once the base value is defined, it can never be redefined. It is a read-only value.

const person = 'Ryan';
person = 'Kristen'; // Uncaught TypeError: Assignment to constant variable.
console.log(person);

The browser will throw an error if you try to reassign a value to a variable defined with const. That being said, you can do something like this.

const person = {
  name: 'Ryan'
};
person.name = 'Kristen';

console.log(person); // {name: 'Kristen'}

Using const does not create an immutable value, the value stored on the person variable is still an object, however we have just changed a property inside of it. If you are looking to lock an object down, look at Object.freeze().

When to use let and when to use const

There is a bit of debate going on right now about when to use let vs const. The general rule of thumb is that if you know the value will not be redefined throughout your program, go with const, if you need a value that might change, go with let. Letting the browser know that a variable will be constant throughout the program will allow it to make certain adjustments, and this could increase performance!

Template Literals

In ES2015 there is a new way to define a string, and it comes with some added benefits. Currently if you want to define a string, you can use '' or "".

let name = "Ryan";
let job = 'Instructor';

If you want to concatenate strings together you can use the + operator.

let name = "Ryan";
let job = "Instructor";
let sentence = name + " works at HackerYou as an " + job;
console.log(sentence); // "Ryan works at HackerYou as an Instructor"

As the amount you need to concatenate grows, this pattern gets pretty tedious and unruly. Enter template literals!

To create a template literal string, we use the backtick ` in place of the quotes.

let name = `Ryan`;
let job = `Instructor`;

They behave exactly the same as a regular string literal, but there is one difference. With a template literal, concatenation becomes a lot easier.

let name = `Ryan`;
let job = `Instructor`;
let sentence = `${name} works at HackerYou as an ${job}`;
console.log(sentence); // "Ryan works at HackerYou as an Instructor"

Notice the ${} syntax inside of the string? This is a template placeholder. It allows us to template out our strings, and the browser will replace the ${} expression with the proper value at runtime. This makes concatenating large strings a lot more enjoyable.

These new placeholders also allow you to carry out expressions inside!

const price = 9.99;
const shipping = 3.99;

const message = `Your total with shipping will be ${price + shipping}.`;

console.log(message); // Your total with shipping will be 13.98.

Multi line

One last thing to look at with template literals is how they can handle multi line strings. With a regular string if you wanted to have it span more than one line, you would have to do something like this.

const multi = "This is a \n multiline string";
console.log(multi);

Including the \n or new line character will force text to go to a new line. If you tried to just put the text on two lines, like this:

const multi = "This is a 
multiline string";
console.log(multi);

It would throw an error Uncaught SyntaxError: Unexpected token ILLEGAL. However with template literals we CAN do just that and add line breaks wherever we’d like!

const multi = `This is a 
multiline string`;
console.log(multi);

This allows us to organize our markup in a way that is considerably cleaner!

const person = {
  name: 'Ryan',
  job: 'Developer/Instructor'
};

const markup = `
  <div>
    <h2>${person.name}</h2>
    <h3>${person.job}</h3>
  </div>
`;

Arrow Functions

Arrow functions are a new syntax for creating functions in ES2015. This does not replace the function() {} syntax that we know and love, but we will be seeing it more and more as the go-to function syntax.

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

The core part of the syntax is the lack of the function keyword when defining a new function. Instead we have the => or fat arrow. You can call the function just as you would any other.

add(2, 3); // 5

There are actually a few ways you can define the arrow function. For example, if the function simply returns a value and there is nothing else in the function body, we can remove the {} and the return keyword.

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

The return here is implicit, meaning that it is implied as opposed to us having to explicitly add return to our block. If the function only had one parameter you can actually leave the () off the definition of the function.

const add5 = a => a + 5;

If there are no parameters to be used in the function, empty parenthesis are used as a placeholder.

const eight = () => 3 + 5;

Or there is a new pattern emerging where people will use a _ as a placeholder in place of the empty parenthesis.

const eight = _ => 3 + 5;

Arrow functions and functional programming

Because the syntax for the arrow function is so small, and most operations in functional programming require very few operations in the function’s body. This syntax is a perfect match for this programming style!

// Without Arrow functions
const numbers = [3,4,5,6,7,8];
const doubleNumbers = numbers.map(function(n) {
  return n * 2;
});

// With arrow functions
const numbers = [3,4,5,6,7,8];
const doubleNumbers = numbers.map( n => n * 2 );

The syntax allows you to make this nice and simple operation into one line!

The this keyword

One place to be cautious of when working with arrow functions is how they handle the this keyword. Consider a method on an object.

const person = {
  firstName: "Ryan",
  sayName: function() {
    return this.firstName;
  }
}
console.log(person.sayName()); // "Ryan"

Inside of the sayName method, the this keyword is bound to the person object. So running the method will produce Ryan. With an arrow function, the this keyword is lexically scoped. This means that the scope of the function will be bound based on where it was defined. The value of this then refers to the parent scope.

const person = {
  firstName: "Ryan",
  sayName: () => {
    return this.firstName; 
  }
}
console.log(person.sayName()); // undefined

In this example, if we changed the sayName method from an anonymous function to an arrow function it will return undefined! The this will be bound lexically, and in this case it will be the window object, on which there is no firstName property. There will be cases where you might want to have that be the correct result! Take a look at this example.

const person = {
  firstName: 'Ryan',
  hobbies: ['Robots', 'Games', 'Internet'],
  showHobbies: function() {
    this.hobbies.forEach(function(hobby) {
      console.log(`${this.firstName} likes ${hobby}`);
    });
  }
};
person.showHobbies();

Running this will produce Uncaught TypeError: Cannot read property 'firstName' of undefined. The this in the callback function for our .forEach() method is bound to nothing(in strict mode, in non strict it will be the window). But if we change the callback to an arrow function we can use the lexically bound this to get the value we want!

const person = {
  firstName: 'Ryan',
  hobbies: ['Robots', 'Games', 'Internet'],
  showHobbies: function() {
    this.hobbies.forEach(hobby => {
      console.log(`${this.firstName} likes ${hobby}`);
    });
  }
};
person.showHobbies();

The this inside of our forEach will be bound to the person object!

Spread Operators

Sometimes we want to do something with an array that we can’t! For example let’s assume we have an array of numbers that we want to find the max of. Math.max seems like the right method for this.

const numbers = [39, 25, 90, 123];
const max = Math.max(numbers);
console.log(max); // NaN

Math.max is a method that takes a comma separated list of values and will return the highest! Sadly we can not pass an array to it. There is a way to get around this though, we can use a method called .apply that takes an array and calls a function as if we had passed them in as a list.

const numbers = [39, 25, 90, 123];
const max = Math.max.apply(null, numbers);
console.log(max); // 123

The first argument in .apply is the value we would like to set the this value for when we call Math.max, in this example we provide null. The second argument is the array we would like to apply to the function. This could be a little confusing, what if there was an easier way to do this?

Enter the Spread Operator

In ES2015 there is the spread operator. The syntax looks like this:

...numbers

What this tool does is spread out, or disperse the elements from the array! It will expand them in place. We can change the above .apply method call to look something like this now.

const numbers = [39, 25, 90, 123];
const max = Math.max(...numbers);
console.log(max); // 123

Spread will expand the array in place and pass the elements in as if it were a comma separated list.

Using the spread operator to concat

You can also use the spread operator to concatenate arrays together! Since spread expands arrays, we can expand arrays in arrays!

const numbersArray1 = [3, 4, 5, 7, 8];
const numbersArray2 = [9, 6, 10, 11];
const concatArray = [...numbersArray1, ...numbersArray2];
console.log(concatArray); // [3, 4, 5, 7, 8, 9, 6, 10, 11]

Rest Parameters

The spread operator allows us to pass an array of arguments into a function. On the flip side of that, rest parameters allows us to gather the parameters passed to our functions! Just like the spread operator the rest parameter syntax also involves the ... at the beginning of a variable name.

Let’s look at an example of this. Imagine we have a function that takes any number of arguments and returns the sum, add(2, 3, 4, 5, 6, 7) would return 27.

const add = function() {
  const numbers = Array.prototype.slice.call(arguments);
  return numbers.reduce((a,b) => a + b);
};
add(2, 3, 4, 5, 6, 7);

Without rest parameters, we would have to use the arguments keyword, and call Array.prototype.slice.call(arguments). What in the world does Array.prototype.slice.call(arguments) mean?! arguments is an Array-LIKE object, meaning it is not an actual array but, is a collection of the arguments passed to a function. However, if we wanted to use an Array method like .reduce() on arguments, we would need to do some fiddling.

JavaScript is built up from a bunch of objects. All of these objects have a parent object that they inherit their methods and properties from. They do this via the .prototype property. Arrays have the .slice method that we can use to create an actual array from our arguments value. Using .call we can call the .slice method from the prototype with arguments as the context to create an array….whoa that is a lot.

Enter rest parameters!

const add = function(...numbers) {
  return numbers.reduce((a, b) => a + b);
};
add(2, 3, 4, 5, 6, 7);

WOW! That was a lot easier. Rest parameters create an actual array from the arguments passed to a function, so we can use methods like .reduce on it. This allows us the freedom to perform similar tasks much easier!

It is important to point out that you can mix and match with rest parameters and the spread operator. Consider a function that takes a multiplier as the first argument, and then will multiply any value after it by that number.

const multi = (multiplier, ...numbers) => {
  return numbers.map(n => n * multiplier);
}

We define the function with a parameter for the multiplier and use rest parameters to collect however many arguments get passed to this function!

JavaScript moving forward

There are a ton of features in ES2015 that we did not go over here, but hopefully this gives you a good basis of some useful new syntax and additions to the language! If you want to learn more, check out my video series Let’s Learn ES6 on YouTube, as well as letslearnes6.com were you can find out about a book I am writing on ES6.