In this tutorial, we will be looking at the differences between ES5 and ES6/ECMAScript 2015 and what they mean to the code you are writing today.
Using ES6 your code will work almost exactly the same in most cases, however writing it will be easier and also makes your code cleaner.
You can see the compatibility of browsers here: ECMAScript 2015 compatibility table.
We will be looking at how to get ES6 working in unsupported browsers below.
Currently there are no browsers that support all the ES6 features, however we have a way to convert the ES6 code we write to the ES5 code that you are writing already. The method for doing this is called Transpiling.
There are currently two major compilers out there today, Babel and Traceur. Most modern projects that are written in ES6 will use one of these compilers to convert the ES6 code to ES5 as part of the build process.
It will actually be converted to the following code so that the browser can understand it:
All you really need to know is that if you are planning to write ES6 code today, then you will need to transpile it, otherwise your code will break when a feature is not supported in a browser.
For setting up transpilation with your project, please see the Babel or Traceur docs above.
In the following sections we will cover the new syntax and features of ES6.
Const and Let keywords
Previously in ES5 you could only define varaibles using the var keyword, however in ES6 there are now two new ways to define varaibles and they all work slightly differently.
What is wrong with using var?
When using var keywords to define variables, what you write in your code is not always hows the code will be interpreted by the complier. Whilst all the variable keywords are susceptible to hoisting Variable Hoisting. There are some inconsistencies to the way var works in block scopes.
What is actually happening here is that the newVar declaration is getting hoisted out of the block scope to the top of the file:
Trying to run the same example using const or let will produce a ReferenceError
This is because we are trying to access newVar outside of its intended scope and we probably want to be informed of that.
Using const and let
So can you just replace all your vars with const and/or let? Ideally yes, but in practice you will probably break your application due to the differences in hoisting. First we need to cover why we have two new keywords and not just one.
Variables that are defined using const as the name suggests are constants and cannot be redefined. Trying to redefine a const will result in a warning and the variable will not change.
It is important to note that const is only protecting its reference from changing. You can still mutate an object that is stored as a const for example:
You couldn’t assign myObject to be a completely different object however.
You should try to use const for everything that is not going to change, such as required modules, function definitions and variables that don’t get overwritten.
Variables that are defined using the let keyword work similarly to how variables defined using var work now. You can change the value of the variable, however the variables respects it scope.
When to use const, let and var
You should try and use const for everything, if you need to change the value of a variable then use let failing both of those types then fall back to using var.
In ES6 it is now possible to import and export modules.
You may have seen something like this before if you have have used Node.js or imported packages from npm.
Now with ES6 we can write the following equivalent code:
The transpilation process will actually convert the above code to the require syntax anyway, so what is the benefit to writing imports this way? Well we do get one nice feature which is named imports.
You may have seen code like this before:
Here we are only using a certain part of the module we are importing, however the whole module is still getting required into our code, we are just assigned a certain part of it to a variable name. With named imports, we can avoid this, it also gives us a nice syntax if we are using multiple parts of a module.
Notice that the import syntax doesn’t look that different from what we did in the in the first ES6 import example, however we have included curly braces, these allow us to specify specific parts of the module (called members) that we want to use. This will create a variable with the same name as the member we imported, however we can also specify an alias if we want the variable to be called something else.
In the above example, we will then have a variable called routeMember that gives us access to the Route module.
There are two different types of export, named exports and default exports.
You can export as many named exports as you want, you can then choose to import each one of these separately when importing the module. Exporting and using named modules would look something like this:
When importing the members, it is important to refer to them using the same name that was exported. Using named exports you can easily split your module in to easily usable parts.
Unlike named exports, you can only have one default export per module and is considered the main exported value of the module.
Notice that the name with the import does not need to match to what was exported here because we are not using a named import.
Arrow functions are one of my favorite ES6 features and I try to use them wherever possible. The premise is simple, remove the need to write the function keyword all the time with a simple two character arrow. But it comes with so many helpful shortcuts it could almost be a tutorial all of its own.
A simple function written in ES5 would look something like this:
We now have a number of different things we can with the function to replace it with an arrow function, you will likely use a mix of these different syntax whilst working, depending on the scenario.
Above, we have shown a number of different ways that you can write arrow functions. They are great to use in anonymous functions as they make you code look a lot cleaner. Consider the following example:
By writing it in ES6 we have not only cut the number of lines down, but our code is more expressive.
However, What if we wanted to return an object from a one line arrow function? Well then we have to use a slightly different syntax due to the curly braces of an object causing confusion between a multi line arrow function and an object.
Using this inside an arrow function
Back in ES5, if we wanted to use the
this keyword inside of a function we used to have to store it as a variable, to then use later on. You may have written code that looks like this before:
With the new arrow functions,
this refers to the parent scope, so the following code would work as expected:
Object Literals Enhancements
There have been a number of changes to how you can specify data inside an object. These are great little timesavers.
If you have a variable that is named the same as the key you want to create in an object, then you now need to only specify the variable name in the object.
Also if you are specifying functions on objects, that has now got a lot easier.
Notice that you no longer need to specify a key, or write the function keyword.
The last thing you can do now, is include dynamic keys inside an object declaration:
We now have the ability to use variables in strings without performing concatenation. This not only makes your string look a lot cleaner when written in code, it also removes the amount of changes needed to the surrounding elements if the variable needs to be moved.
Note the use of backticks (`) surrounding the string rather than quotations. variables are also wrapped in braces and have a dollar symbol at the beginning. These will then be replaced at run time with the correct values.
Previously when writing ES5 code, if you wanted to store one or more variables from an object, you would probably need at least two lines of code, destructing in ES6 makes this a lot cleaner.
I now have two variables called name and country that will hold the values of that object.
If I wanted the variables named differently then I can specify an alias:
So I am still looking up the name and country keys, but storing them under the myName and myCountry variables respectively.
Functions that accept parameters can have default values assigned to the variables.
You may have found yourself doing something like this back in ES5:
For the most part this works, however you can’t immediately see that these values are getting overwritten if they are no passed in.
Now with ES6 you can pass default values directly into the parameter declaration, similar to how it would be done in a language like PHP.
This not only makes it clearer that the parameters are optional, but makes the function much smaller also.
Generators are functions that can be called, exited and then re-entered at a later time. Calling a generator does not immediately execute the function, but rather returns an iterator that you can them call
.next() on to step through the function.
A simple example of a generator would be:
Note the asterisk (*) next to the function declartation, this denotes that the function is a generator.
Whilst we are using a while loop inside the generator function that would normally complete instantly as soon as the function is called, because we are calling yield inside the generator we stop execution of that function until next time we call
Note that the last call to
.next() returns undefined for the value. The return for
.next() returns an object, there is also a key of
done on the object that is normally false until the last execution at which point it becomes true. Using this key you can know that the generator has completed all its yield statements.
You can also yield other generators, however it is slightly beyond the scope of this tutorial.
ES6 brings with a new way to define classes, which not only makes it easier to work with classes, but also a lot less typing. The same class definition in ES6 would look something like this:
As you can see, the usage is exactly the same, however our class definition is now much more concise and clear.
You can also extend classes by using the extends keyword, this enables you to have a common base for shared classes, but allow the child classes to overwrite functions or add new ones entirely.
Promises are now standard as of ES6 however, they have been around for a while with the help of various libraries. We are going to cover Promises in a separate tutorial as they are too big to be covered here.
This tutorial covers the main features of ES6, as you can see there are a number of improvements and changes that are going to drastically change how you write code.
I hope you found this intro to ES6 helpful, look out for more tutorials shortly, where we will be using ES6 in practice.