Top ES6 feature one should know
So ES6, sounds a new cool name around the corner in tech distrct. If you are still not aware what ES6 is, its a new and improved JavaScript version over ES5. Here are some top feature that a Javascript developer should know.
This post is originally published here on April 15, 2017
1.) Promises
2.) Template Literals and Multi-line Strings
3.) Block-Scoped Constructs
4.) Enhanced Object Literals
5.) Arrow Functions
6.) Default Parameters
7.) Assignments
8.) Classes
9.) Modules
1.) Promises
In JS world, promise
s always been a controversial topic. There are lots of libraries out there implementing promise in there tastes. jquery’s deferred, q, bluebird, deferred.js to name just a few. Opinions conflict over using async, generators, callbacks etc. ECMA has officially implemented promises in ES6. That’s a good news for JS community over having a standard implementation of promises.
Let’s consider a trivial example in plain old ES5
var random = function (resolve, reject){
if (Math.random() * 10 <= 9) {
resolve('Yay!');
} else{
reject(new Error('I failed!'));
}
};
random(
function (resolveValue){ console.log(resolveValue) },
function (error){ console.log(error) }
);
This can be rewriten using promises in es6, as below
new Promise ((resolve, reject) => {
if (Math.random() * 10 <= 9) {
resolve('Yay!');
}
reject(new Error('I failed!'));
}).then (
(resolveValue) => { console.log(resolveValue) },
(error) => { console.log(error) }
)
So far, we’ve mostly same number of lines of code without any visible obvious benefit. But this would benefit in case there are lots of nested calls that could rather result is famous callback hell.
ES6 also has fail-and-catch-all
callback, that seems to be a good improvement over handling and catching them in every implementation.
We would dive deeper into ‘Promises in ES6’ in future posts. So keep tuned here.
2.) Template Literals/Interpolation and Multiline strings
In my honest opinion, template literals in JS were long overdue. It’s tough for a developer to use +
again and again to concat string for the sole purpose of string interpolation.
And believe me after few long interpolation it becomes messy to maintain and looks bad.
Here is how it was done in ES5 or what we are forced to use
var first_name = 'Pankaj',
last_name = 'Baagwan'
var full_name = function(){
console.log("We got your full name: It is " + first_name + ' ' + last_name);
};
But in ES6, it can be done easily and looks beautiful. This can be done using ${VAR_HERE} withing back-ticked string. Have a look
var full_name = ()=> {
console.log(`We got your full name: It is ${first_name} ${last_name}`)
};
Similarly, es6 gives ability to write multiline strings using backticks. Currently we have to use +
for concatenation and some other measures to have a multiline strings. Have a look
var lipsum = 'Mauris tempus velit at sapien euismod placerat.\n'
+ 'Donec commodo dui sit amet imperdiet tincidunt.\n'
+ 'Ut blandit, et tincidunt, nisl enim ornare lacus\n'
+ 'eget pellentesque ex libero eget lacus.\n'
+ 'Praesent luctus mauris, eu dictum elit ultricies eu.\n'
+ 'Etiam viverra consectetur bibendum at nec turpis.\n'
+ 'Cras at porttitor tellus, vitae dapibus justo.\n'
+ 'Nullam eget neque at elementum egestas eget et elit.\n'
+ 'Proin blandit euismod. Suspendisse malesuada erat.'
and in es6, it delightfully simple to curate multiline strings, whatever written within backticks
var lipsum = `Mauris tempus velit at sapien euismod placerat.
Donec commodo dui sit amet imperdiet tincidunt.
Ut blandit, et tincidunt, nisl enim ornare lacus
eget pellentesque ex libero eget lacus.
Praesent luctus mauris, eu dictum elit ultricies eu.
Etiam viverra consectetur bibendum at nec turpis.
Cras at porttitor tellus, vitae dapibus justo.
Nullam eget neque at elementum egestas eget et elit.
Proin blandit euismod. Suspendisse malesuada erat.`
3.) Block-Scoped Constructs
So first thing that caught my eye while going through es6 outline is script. My initial thought was, “Why do we need another var
?”. But after lots of digging I came to conclusion that its different and for good.
let
is new var
, which restricts visibility/scope of variable at block level instead of at immediate function level; that was the case with var
So, finally we can have some predictability on visibility, though in my opinion there is long way to go for javascript to evolve further.
function sayHello(){
if(true){
var hello = "Hello";
}
return hello;
};
console.log(sayHello());
The construct above should technically be errornous and hello
should not be visible outside if
block. But is ES5; we are living with this. ES5, by default have function level scope and any variable declared within function scope; moves to the top; hence hello
has visibility outside if
block within sayHello
function
To overcome, this ES6 has introduced let
, though var
stays with same visibility rules. So let
can be used to restrict visibility of variable in es6 as needed. Now replace var
with let
in sayHello
function. Let’s have a look
function sayHello(){
if(true){
let hello = "Hello";
}
return hello;
};
console.log(sayHello());
ES6 has another construct that is also block scoped and called const
; it’s an immutable as well. To demonstrate, here are a bunch of constants and they all are okay because they belong to different blocks:
function sayHello(){
const hello = "Hello ES6";
if(true){
const hello = "Hello World";
}
return hello;
};
console.log(sayHello());
When executed, it should print Hello ES6
since that’s what visible.
In my opinion, though let
and const
are welcome but letting var
stay will add to confusion and lot will need to be considered for choosing one over other.
4.) Enhanced Object Literals
ES5 has glorified JSON, but it was tough mapping key values of one object to other. Here is a typical ES% object literal example
var apiServer = { host: "example.com", port: 80 },
sayHello = function (){ "Hello ES5"; };
serverConfig = {
host: apiServer.host,
port: apiServer.port,
endpoint: function (){
return "http://" + this.host + ':' + this.port;
},
sayHello: sayHello,
toString: function (){
return JSON.stringify(this.valueOf());
}
};
serverConfig.toString();
//=> "{"host":"example.com","port":80}"
Other simpler way to create this in ES5, is to inherit apiServer
into serverConfig
byt using Object.create()
In ES6 object literal, there are shorthands for assignment sayHello: sayHello,
becomes just sayHello,
. Also, we can set the prototype right there in the __proto__
property which is more sensical.
var apiServer = { host: "example.com", port: 80 },
sayHello = function (){ "Hello ES5"; };
serverConfig = {
__proto__: apiServer,
endpoint: function (){
return "http://" + this.host + ':' + this.port;
},
sayHello,
toString: function (){
return JSON.stringify(this.valueOf());
}
};
This feels more right approach and shorthand than what was to be done in ES5.
5.) Arrow Functions
Though arrow function seems like a syntatic sugar, but it comes handy given how function used to be declared/defined in ES5 or before.
One more thing to be noticed that this
would behave properly or as expected when within arrow function, its immutable withing function context, unless you create a closuer.
That means that one do not have to use bind
, apply
etc while calling a function to change this
behaviour
Let’s have a look at example
// ES5 function context
// 1st variant
var _this = this;
this.nums.forEach(function (v){
if (v%5 === 0)
_this.fives.push(v)
});// 2nd variant in ES5
this.nums.forEach(function (v){
if (v%5 === 0)
this.fives.push(v)
}, this);// 3rd variant in ES5
this.nums.forEach(function (v){
if (v%5 === 0)
this.fives.push(v)
}.bind(this));
In above snippet, we have seen several ways we used to achieve lexical scoping of this. Now let’s see how it can be achieved in ES6, using arrow functions
Note: function
scoping in ES6 still behave in similar way
this.nums.forEach((v) => {
if (v%5 === 0)
this.fives.push(v)
});
Neat, isn’t it? So how exactly this is achieved, Lexical scoping uses this
from the code that declares arrow function.
6.) Default Parameters
Remember, the struggle that a developer had to do to assign default values in ES5 or before. Let’s recall
function multiply (a, b){
a = a || 5;
b = b || 3;
return a*b;
};
multiply();
//=> 15
multiply(4);
//=> 12
multiply(4,5);
//=> 20
multiply(0, 0);
//=> 15 WAAT?
What?, 0 multiplied by 0 is 15? In ES5 or before yes, because 0 is falsy in javascript and hence it would have default value assigned, so 15 is the result.
But with ES6, this is no more. we can put default value in function signature itself. Let’s have a look
function multiply (a = 5, b = 3){
return a*b;
};
multiply();
//=> 15
multiply(4);
//=> 12
multiply(4,5);
//=> 20
multiply(0, 0);
//=> 0
Neat. This is what expected and that’s what we got.
7.) Assignments
The two most commonly used data structures in JavaScript are Object and Array. Objects allow us to pack pieces of information into a single entity and arrays allow us to store ordered collections.
Destructuring assignment is a special syntax that allows us to “unpack” arrays or objects into a bunch of variables, as sometimes they are more convenient. Destructuring also works great with complex functions that have a lot of parameters, default values, and soon we’ll see how these are handled too.
In ES5, destructuring or assigment was tough, Let’s recall
var array = ["Hello", "I", "am", "Pankaj", "Bagwan"],
firstName = array[3],
lastName = array[4];
That’s tough and tiring to code, given that one has long array or object to destructure. In ES6, though it is easy to destructure and assign array and objects alike. Have a look
let array = ["Hello", "I", "am", "Pankaj", "Bagwan"];
let [,,,firstName, lastName] = array
firstName
//=> "Pankaj"
lastName
//=> "Bagwan"
First three commas, actually ignores what’s at that index in array on right hand side. There is another special assigment strategy with three dots followed by variable name that captures rest values in an another array.
let array = ["Hello", "I", "am", "Pankaj", "Bagwan"];
let [greet, ...intro] = array
greet
//=> "Hello"
intro
//=> (4) ["I", "am", "Pankaj", "Bagwan"]
The only condition is that rest variable should be last in element in assignment. For example below code would throug an error
let array = ["Hello", "I", "am", "Pankaj", "Bagwan"];
let [...rest, firstName, lastName] = array
// Uncaught SyntaxError: Rest element must be last element
Though we would discuss destructuring and assignment in detail in some future post. But few things to note are
- It does not mutate original array
- Works with any iterable on the right-hand-side
- Assigns to anything at the left-hand-side
8.) Classes
Until ES6, Javascript follows Prototypal inheritance model and that’s sometime confusing to people coming from OOP’s technologies (i.e Java, Ruby etc)
In ES6, It makes writing classes and inheriting from them as easy as liking a comment on Social media.
We wont go into details on how it was done in ES5 or before because there is not one standard or flavour. And, not to mention; discussing that may trigger javascript religious war.
Instead, we would go straight to how ES6 handles it
class Polygon {
constructor(height, width){
this.name = "Polygon";
this.height = height;
this.width = width;
} who (){
console.log(`Hi, I am ${this.name}`);
} area (){
return "Generic class, Not implemented."
}
}
Looks neat? right. There are few noticable changes
- Attribute values can only be (re)assigned in method (and constructor), not at class level
- Method do not need to have keyword function
ES6 allows class to be inherited into other class by using keyword extends
class Square extends Polygon {
constructor(length){
super(length, length)
this.name = "Square";
} area (){
return this.height * this.width;
}
}
That’s neat as well. So when a method is called, it will first look for implementation in its class, if not found than traverse upward. Like in Square
class method who
is not defined, but when called it will find method in its parent class and execute same.
9.) Modules
If one recalls, there is no module support in ES5 or before. And since there are no standardization, people came up with there own implementation e.g RequireJS, CommonJS and other workarounds
Luckily, in ES6, we have module support built-in. Since there is no standard implementation in ES5 or before, we are directly going to dive into ES6 implementation
Note: Other non-standard implementation like CommonJS are out of the scope of this post, so we are not going to cover them
export const host = 'example.com';
export const port = 8000;
export function baseUrl () {
...
}
export default class ApiService {
...
}
This is how ES6 exports modules. In a JS file anything that is defined with keyword export
is eligible to be exposed to other modules implementation.
We used word eligible, because its not exported by default whenever you import that module. Default export is defined with export default
and that’s what is imported by default
ES6 permits to export desired members in single statement at the end of the module, like
export { host, port, baseUrl, ApiService };
ES6 allows to alias export, like
export { host as uri, port, baseUrl, ApiService };
To import, ES6 has elegant and meaningful keyword import
e.g.
import { host as uri, port, ApiService } from 'api.js';
One can import all exported member by using *
sign
import * as Api from 'api.js';
That’s it for this post. Have a good day and Happy coding. We would discuss these in details in future posts, so stay tuned