This is a series of posts that will help familiarize you with many of the features of the recently-approved ECMAScript 6 specification (aka ES2015, ES Harmony, ESNext) as well as go through some of the proposed changes for ES6+/ES7.
It will focus on providing examples of how your code changes before and after applying the new ES6 and beyond features.
Note: ECMAScript is the “official” name of the JavaScript language specification.
All browsers updated in the past few years support the previous version (ES5), and the latest versions of many browsers support much (but not all) of ES6.
When using ES6 code in production, it is recommended that you first pass it through a transpiler such as Babel which will turn your ES6 code into ES5 code for maximum browser support.
See this compatibility table for more information about which environments support ES6 features natively.
Part 1 - Sugar
This post will focus on the sweeter side of ES6 – the syntactic sugar.
These are features that can be written in ES5 without the addition of helper libraries, but have new syntax in ES6 that can often make your code shorter and clearer.
Template Strings
Template strings a new type of string literal which eases the creation of multi-line strings, allow for interpolation inside of the string and, ends once and for all the “single quote or double quote debate”.
Template strings use the backtick ( ` ) operator to start and close the string.
Multi Line Strings
If you’ve ever had to build an HTML in your JavaScript then you’ve encountered the problem of not having “real” multi-line strings available at your disposal.
You’ve either had to use string concatenation with + or added them to an array and joined them at the end. Template strings solve this problem by allowing you to use multi-line strings.
Template strings allow you to use both single and double quotes without having to escape them.
The bad news is that if you need a backtick in your template strings you still will need to escape them, but that should happen much less frequently.
Before
1
varsentence="I've got one word for template strings: \"Great\"";
Perhaps the best new feature of template strings is the ability to perform interpolation.
Similar to other languages that support interpolation, JavaScript template strings allow you to capture the result of an expression directly in your string without having to rely on manual concatenation or string replaces.
By wrapping the expression in your template string inside a ${} you mark what things are to be replaced in your string.
Before
1
2
3
4
5
6
7
functiongreet(name){return'Welcome, '+name+'! The time is '+getTime()+'.';}functiongetTime(){returnnewDate();}
ES6 has several features that eliminate the boilerplate needed for some common tasks when creating/calling functions: default parameters, rest parameters, and spread operators.
Default Parameters
Often times you’ll create a function that accepts multiple parameters, some of which are optional and have defaults.
The non-ES6 way of doing this would be to check if the argument is undefined and then set the value in that case.
In ES6 this is added directly to the language in ES6 in the function definition.
ES6 default parameters are evaluated at call time, so you can put any expression in them, including method calls and they will be evaluated when the function is called.
Note that default parameters in ES6 do not extend to the inner contents of objects – if an object is passed to a function with a default parameter of an object, it will not “merge” in the inner values of that default.
Before
1
2
3
4
5
6
7
8
9
10
11
functiongetDefaultAccountId(){return42;}functiongetAccount(accountId,includeUser,options){if(accountId===undefined)accountId=getDefaultAccountId();if(includeUser===undefined)includeUser=true;if(options===undefined)options={timeout:30000};// logic to get the account}
After
1
2
3
4
5
6
7
functiongetDefaultAccountId(){return42;}functiongetAccount(accountId=getDefaultAccountId(),includeUser=true,options={timeout:30000}){// logic to get the account}
Rest Parameters
Rest parameters allow you to represent a variable number of arguments to a function call as an Array.
The ES5 way of achieving this is by using the arguments object and mapping the contents to a real Array.
By using rest parameters you can be more explicit about the expected function parameters as well as avoid the boilerplate conversion of arguments into an Array.
Before
1
2
3
4
5
6
7
8
9
10
11
12
functiongetUsers(userType){varuserIds=Array.prototype.splice.call(arguments,getUsers.length);// convert arguments to a "real" Array.returnuserIds.map(function(userId){returngetUser(userType,userId);});}functiongetUsers(userType,userId){// a function that gets the user based on userType and user id }getUsers('ADMIN',1,2,3);
After
1
2
3
4
5
6
7
8
9
10
11
functiongetUsers(userType,...userIds){returnuserIds.map(function(userId){returngetUser(userType,userId);});}functiongetUsers(userType,userId){// a function that gets the user based on userType and user id }getUsers('ADMIN',1,2,3);
Spread Operators
Spread operators can kind of be considered the inverse of rest parameters – instead of gathering multiple parameters into a single array it spreads the contents of a single array into multiple parameters.
In many places where you previously used “apply” you can use the spread operator instead.
One common use is when you want to push several items on to the end of an existing array.
Before
1
2
3
4
5
6
7
8
// pushes all items from the newItems array onto the items listfunctionpushAll(items,newItems){items.push.apply(items,newItems);}varitems=[1,2,3];varotherItems=[4,5,6];pushAll(items,otherItems);// items: [1, 2, 3, 4, 5, 6];
After
1
2
3
4
5
6
7
8
// pushes all items from the newItems array onto the items listfunctionpushAll(items,newItems){items.push(...newItems);}varitems=[1,2,3];varotherItems=[4,5,6];pushAll(items,otherItems);// items: [1, 2, 3, 4, 5, 6];
Because apply cannot be used with constructor functions, the spread operator is perfect for them.
Before
1
2
3
4
5
6
7
8
9
10
11
12
functionconstructDate(dateArray){if(dateArray.length===3){// [1999, 11, 31]returnnewDate(dateArray[0],dateArray[1],dateArray[2]);}elseif(dateArray.length===4){returnnewDate(dateArray[0],dateArray[1],dateArray[2],dateArray[3]);}elseif(dateArray.length===5){returnnewDate(dateArray[0],dateArray[1],dateArray[2],dateArray[3],dateArray[4]);}// etc...}vardateArray=getDate();// gets a date as an arrayvardate=constructDate(dateArray);
After
1
2
vardateArray=getDate();// gets a date as an arrayvardate=newDate(...dateArray);
Destructuring Assignment
ES6 introduces a type of shorthand called destructuring, which uses pattern matching to assign the contents of an array or object into individual variables.
The left hand side of the expression is matched against the contents of the right hand side of the expression.
This can reduce the number of statements you have to write in order to pull values out of an object or array.
In many cases this eliminates the need to make a temporary variable in order to extract variables from the result of a method call.
Array Destructuring
Array destructuring is used to pull variables out of an array based on their position within the array.
Before
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// parses a date in the form of "YYYY-MM-DD hh:mm:ss"functionparseDate(dateStr){varparts=dateStr.split(' ');vardate=parts[0];vardateParts=date.split('-').map(Number);varyear=dateParts[0];varmonth=dateParts[1];varday=dateParts[2];vartime=parts[1];vartimeParts=time.split(':').map(Number);varhours=timeParts[0];varmins=timeParts[1];varseconds=timeParts[2];returnnewDate(year,month-1,day,hours,mins,seconds);}
After
1
2
3
4
5
6
7
// parses a date in the form of "YYYY-MM-DD hh:mm:ss"functionparseDate(dateStr){var[date,time]=dateStr.split(' ');var[year,month,day]=date.split('-').map(Number);var[hours,mins,seconds]=time.split(':').map(Number);returnnewDate(year,month-1,day,hours,mins,seconds);}
Object Destructuring
Similar to array destructuring, object destructuring is pulls variables out based on their property name and assigns them to an array.
It is especially useful when working with a module system such as CommonJS (or the ES6 module system) where a set of related functions is bundled into an object but you may only need a few of them.
Object literals have been enhanced in ES6 with sugar that makes writing them less verbose.
Property Shorthand
Property shorthand provides a quick way to create objects from a set of variables. The name of the variable will be used as both the key and the value for the property.
This is useful in situations where you have a list of variables that you want to combine into a single object, such as in a factory function.
Method shorthand is similar to property shorthand in that it simply reduces the number of characters needed to type when defining a method on an object: the “function” part of the method declaration can be dropped.
Before
1
2
3
4
5
6
7
8
functioncreatePerson(firstName){return{firstName:firstName,greet:function(greeting){returngreeting+' my name is '+this.firstName;}};}
After
1
2
3
4
5
6
7
8
functioncreatePerson(firstName){return{firstName:firstName,greet(greeting){returngreeting+' my name is '+this.firstName;}};}
Computed/Dynamic Properties
Have you ever needed to add a property to an object when creating whose key is based on some other variable or function?
In ES5 you would have to create the object and then add the new property to the object.
With ES6 computed properties you can add statements directly to the key name so you can create the object without dropping out of the literal notation.
Dynamic properties are created by wrapping the statement inside of square braces: [].
Perfect when creating objects based on other constants.
Arrow functions (sometimes referred to as “fat arrows” =>) in ES6 are a new way to declare functions that “share” the same this context as the surrounding scope.
The basic syntax is that the left hand side of the arrow contains the function arguments and the right hand side contains the statement/expressions.
They are similar in syntax to lambda expressions found in many other languages and are ideal for “functional” programming style.
Shorthand
Parentheses on the left hand side of the arrow are optional if there is only one argument, and curly braces on the right hand side are optional if there is a single expression (in this case, the single expression will be returned from the function).
Before
1
2
3
4
5
6
7
8
9
10
// doubles all numbers, excludes all doubled numbers who do not contain the '4' digit in them, and sums the leftover numbers togetherfunctionsillyMathProblem(numbers){returnnumbers.map(function(number){returnnumber*2;}).filter(function(number){returnnumber.toString().indexOf('4')!==-1;}).reduce(function(l,r){returnl+r;},0);}
After
1
2
3
4
5
6
// doubles all numbers, excludes all doubled numbers who do not contain the '4' digit in them, and sums the leftover numbers togetherfunctionsillyMathProblem(numbers){returnnumbers.map(number=>number*2).filter(number=>number.toString().indexOf('4')!==-1).reduce((l,r)=>l+r,0);}
Lexical ‘this’
Prior to the introduction of arrow functions, all functions defined their own “this” value.
This meant that 1) you could never really know for sure what “this” referred to in a function call without seeing how it was actually called and 2) you often had to create temporary variables or use .bind or .call in order to preserve a “this” from an outer scope.
With arrow functions, the “this” variable is the same as the outer scope, and can never be changed.
Before
1
2
3
4
5
6
7
8
9
10
11
12
13
14
functionUsersService(){varusers={};this.getUser=function(userId){returnusers[userId];}this.getUsers=function(userIds){varself=this;returnuserIds.map(function(userId){// or could have used .bind(this) instead of using selfreturnself.getUser(userId);});};}
After
1
2
3
4
5
6
7
8
9
10
11
functionUsersService(){varusers={};this.getUser=function(userId){returnusers[userId];}this.getUsers=function(userIds){returnuserIds.map(userId=>this.getUser(userId));// maintains the "this" of the containing scope};}
Conclusion
ES6 provides a whole host of new syntactic sugar that can be used to shorten and clarify your code.
However, ES6 is not all about the sweet stuff.
In the next few “ES6 and Beyond” posts we’ll discuss features that are not merely shortened versions of current ES5 features.
These features include things from the ES6 standard such as classes, Promises, Symbols, iterators, and generators/yield as well as candidate features for JavaScript versions
beyond ES6 that have the potential to completely change the way you write your code: decorators, async, and observables.
Author
Paul Selden https://github.com/pselden
Paul is a Senior Software Engineer at OpenX and works remotely from Dexter, MI. He spends most of his free time playing with his twin toddlers.