Chapter 6. ECMAScript 6
So far, we have taken a detailed tour of the JavaScript programming language. I am sure that you must have gained significant insight into the core of the language. What we saw so far was as per the ECMAScript 5 (ES5) standards. ECMAScript 6 (ES6) or ECMAScript 2015 (ES2015) is the latest version of the ECMAScript standard. This standard is evolving and the last round of modifications was done in June, 2015. ES2015 is significant in its scope and the recommendations of ES2015 are being implemented in most JavaScript engines. This is great news. ES6 introduces a huge number of features that add syntactic forms and helpers that enrich the language significantly. The pace at which ECMAScript standards keep evolving makes it a bit difficult for browsers and JavaScript engines to support new features. It is also a practical reality that most programmers have to write code that can be supported by older browsers. The notorious Internet Explorer 6 was once the most widely used browser in the world. To make sure that your code is compatible with the most number of browsers is a daunting task. So, while you want to jump to the next set of awesome ES6 features, you will have to consider the fact that several ES6 features may not be supported by the most popular of browsers or JavaScript frameworks.
This may look like a dire scenario, but things are not that dark. Node.js uses the latest version of the V8 engine that supports majority of ES6 features. Facebook's React also supports them. Mozilla Firefox and Google Chrome are two of the most used browsers today and they support a majority of ES6 features.
To avoid such pitfalls and unpredictability, certain solutions have been proposed. The most useful among these are polyfills/shims and transpilers.
Shims or polyfills
Polyfills (also known as shims) are patterns to define behavior from a new version in a compatible form supported by an older version of the environment. There's a great collection of ES6 shims called ES6 shim (https://github.com/paulmillr/es6-shim/); I would highly recommend a study of these shims. From the ES6 shim collection, consider the following example of a shim.
The Number.isFinite()
method of the ECMAScript 2015 (ES6) standard determines whether the passed value is a finite number. The equivalent shim for it would look something as follows:
var numberIsFinite = Number.isFinite || function isFinite(value) { return typeof value === 'number' && globalIsFinite(value); };
The shim first checks if the Number.isFinite()
method is available; if not, it fills it up with an implementation. This is a pretty nifty technique to fill in gaps in specifications. Shims are constantly upgraded with newer features and, hence, it is a sound strategy to keep the most updated shims in your project.
Note
The endsWith()
polyfill is described in detail at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith. String.endsWith()
is part of ES6 but can be polyfilled easily for pre-ES6 environments.
Shims, however, cannot polyfill syntactical changes. For this, we can consider transpilers as an option.
Transpilers
Transpiling is a technique that combines both compilation and transformation. The idea is to write ES6-compatible code and use a tool that transpiles this code into a valid and equivalent ES5 code. We will be looking at the most complete and popular transpiler for ES6 called Babel (https://babeljs.io/).
Babel can be used in various ways. You can install it as a node module and invoke it from the command line or import it as a script in your web page. Babel's setup is exhaustive and well-documented at https://babeljs.io/docs/setup/. Babel also has a great Read-Eval-Print-Loop (REPL). We will Babel REPL for most of the examples in this chapter. An in-depth understanding of various ways in which Babel can be used is out of the scope of this book. However, I would urge you to start using Babel as part of your development workflow.
We will cover the most important part of ES6 specifications in this chapter. You should explore all the features of ES6 if possible and make them part of your development workflow.
ES6 syntax changes
ES6 brings in significant syntactic changes to JavaScript. These changes need careful study and some getting used to. In this section, we will study some of the most important syntax changes and see how you can use Babel to start using these newer constructs in your code right away.
Block scoping
We discussed earlier that the variables in JavaScript are function-scoped. Variables created in a nested scope are available to the entire function. Several programming languages provide you with a default block scope where any variable declared within a block of code (usually delimited by {}
) is scoped (available) only within this block. To achieve a similar block scope in JavaScript, a prevalent method is to use
immediately-invoked function expressions (IIFE). Consider the following example:
var a = 1; (function blockscope(){ var a = 2; console.log(a); // 2 })(); console.log(a); // 1
Using the IIFE, we are creating a block scope for the a
variable. When a variable is declared in the IIFE, its scope is restricted within the function. This is the traditional way of simulating the block scope. ES6 supports block scoping without using IIFEs. In ES6, you can enclose any statement(s) in a block defined by {}
. Instead of using var
, you can declare a variable using let
to define the block scope. The preceding example can be rewritten using ES6 block scopes as follows:
"use strict"; var a = 1; { let a = 2; console.log( a ); // 2 } console.log( a ); // 1
Using standalone brackets {}
may seem unusual in JavaScript, but this convention is fairly common to create a block scope in many languages. The block scope kicks in other constructs such as if { }
or for (){ }
as well.
When you use a block scope in this way, it is generally preferred to put the variable declaration on top of the block. One difference between variables declared using var
and let
is that variables declared with var
are attached to the entire function scope, while variables declared using let
are attached to the block scope and they are not initialized until they appear in the block. Hence, you cannot access a variable declared with let
earlier than its declaration, whereas with variables declared using var
, the ordering doesn't matter:
function fooey() { console.log(foo); // ReferenceError let foo = 5000; }
One specific use of let
is in for loops. When we use a variable declared using var
in a for loop, it is created in the global or parent scope. We can create a block-scoped variable in the for loop scope by declaring a variable using let
. Consider the following example:
for (let i = 0; i<5; i++) { console.log(i); } console.log(i); // i is not defined
As i
is created using let
, it is scoped in the for
loop. You can see that the variable is not available outside the scope.
One more use of block scopes in ES6 is the ability to create constants. Using the const
keyword, you can create constants in the block scope. Once the value is set, you cannot change the value of such a constant:
if(true){ const a=1; console.log(a); a=100; ///"a" is read-only, you will get a TypeError }
A constant has to be initialized while being declared. The same block scope rules apply to functions also. When a function is declared inside a block, it is available only within that scope.
Default parameters
Defaulting is very common. You always set some default value to parameters passed to a function or variables that you initialize. You may have seen code similar to the following:
function sum(a,b){ a = a || 0; b = b || 0; return (a+b); } console.log(sum(9,9)); //18 console.log(sum(9)); //9
Here, we are using ||
(the OR operator) to default variables a
and b
to 0
if no value was supplied when this function was invoked. With ES6, you have a standard way of defaulting function arguments. The preceding example can be rewritten as follows:
function sum(a=0, b=0){ return (a+b); } console.log(sum(9,9)); //18 console.log(sum(9)); //9
You can pass any valid expression or function call as part of the default parameter list.
Spread and rest
ES6 has a new operator, …
. Based on how it is used, it is called either spread
or rest
. Let's look at a trivial example:
function print(a, b){ console.log(a,b); } print(...[1,2]); //1,2
What's happening here is that when you add …
before an array (or an iterable) it spreads the element of the array in individual variables in the function parameters. The a
and b
function parameters were assigned two values from the array when it was spread out. Extra parameters are ignored while spreading an array:
print(...[1,2,3 ]); //1,2
This would still print 1
and 2
because there are only two functional parameters available. Spreads can be used in other places also, such as array assignments:
var a = [1,2]; var b = [ 0, ...a, 3 ]; console.log( b ); //[0,1,2,3]
There is another use of the …
operator that is the very opposite of the one that we just saw. Instead of spreading the values, the same operator can gather them into one:
function print (a,...b){ console.log(a,b); } console.log(print(1,2,3,4,5,6,7)); //1 [2,3,4,5,6,7]
In this case, the variable b
takes the rest of the values. The a
variable took the first value as 1
and b
took the rest of the values as an array.
Destructuring
If you have worked on a functional language such as Erlang, you will relate to the concept of pattern matching. Destructuring in JavaScript is something very similar. Destructuring allows you to bind values to variables using pattern matching. Consider the following example:
var [start, end] = [0,5];
for (let i=start; i<end; i++){
console.log(i);
}
//prints - 0,1,2,3,4
We are assigning two variables with the help of array destructuring:
var [start, end] = [0,5];
As shown in the preceding example, we want the pattern to match when the first value is assigned to the first variable (start
) and the second value is assigned to the second variable (end
). Consider the following snippet to see how the destructuring of array elements works:
function fn() { return [1,2,3]; } var [a,b,c]=fn(); console.log(a,b,c); //1 2 3 //We can skip one of them var [d,,f]=fn(); console.log(d,f); //1 3 //Rest of the values are not used var [e,] = fn(); console.log(e); //1
Let's discuss how objects' destructuring works. Let's say that you have a function f
that returns an object as follows:
function f() { return { a: 'a', b: 'b', c: 'c' }; }
When we destructure the object being returned by this function, we can use the similar syntax as we saw earlier; the difference is that we use {}
instead of []
:
var { a: a, b: b, c: c } = f(); console.log(a,b,c); //a b c
Similar to arrays, we use pattern matching to assign variables to their corresponding values returned by the function. There is an even shorter way of writing this if you are using the same variable as the one being matched. The following example would do just fine:
var { a,b,c } = f();
However, you would mostly be using a different variable name from the one being returned by the function. It is important to remember that the syntax is source: destination and not the usual destination: source. Carefully observe the following example:
//this is target: source - which is incorrect var { x: a, x: b, x: c } = f(); console.log(x,y,z); //x is undefined, y is undefined z is undefined //this is source: target - correct var { a: x, b: y, c: z } = f(); console.log(x,y,z); // a b c
This is the opposite of the target = source way of assigning values and hence will take some time in getting used to.
Object literals
Object literals are everywhere in JavaScript. You would think that there is no scope of improvement there. However, ES6 wants to improve this too. ES6 introduces several shortcuts to create a concise syntax around object literals:
var firstname = "Albert", lastname = "Einstein", person = { firstname: firstname, lastname: lastname };
If you intend to use the same property name as the variable that you are assigning, you can use the concise property notation of ES6:
var firstname = "Albert", lastname = "Einstein", person = { firstname, lastname };
Similarly, you are assigning functions to properties as follows:
var person = { getName: function(){ // .. }, getAge: function(){ //.. } }
Instead of the preceding lines, you can say the following:
var person = { getName(){ // .. }, getAge(){ //.. } }
Template literals
I am sure you have done things such as the following:
function SuperLogger(level, clazz, msg){ console.log(level+": Exception happened in class:"+clazz+" - Exception :"+ msg); }
This is a very common way of replacing variable values to form a string literal. ES6 provides you with a new type of string literal using the backtick (`
) delimiter. You can use string interpolation to put placeholders in a template string literal. The placeholders will be parsed and evaluated.
The preceding example can be rewritten as follows:
function SuperLogger(level, clazz, msg){ console.log(`${level} : Exception happened in class: ${clazz} - Exception : {$msg}`); }
We are using ``
around a string literal. Within this literal, any expression of the ${..}
form is parsed immediately. This parsing is called interpolation. While parsing, the variable's value replaces the placeholder within ${}
. The resulting string is just a normal string with the placeholders replaced with actual variable values.
With string interpolation, you can split a string into multiple lines also, as shown in the following code (very similar to Python):
var quote = `Good night, good night! Parting is such sweet sorrow, that I shall say good night till it be morrow.`; console.log( quote );
You can use function calls or valid JavaScript expressions as part of the string interpolation:
function sum(a,b){ console.log(`The sum seems to be ${a + b}`); } sum(1,2); //The sum seems to be 3
The final variation of the template strings is called tagged template string. The idea is to modify the template string using a function. Consider the following example:
function emmy(key, ...values){
console.log(key);
console.log(values);
}
let category="Best Movie";
let movie="Adventures in ES6";
emmy`And the award for ${category} goes to ${movie}`;
//["And the award for "," goes to ",""]
//["Best Movie","Adventures in ES6"]
The strangest part is when we call the emmy
function with the template literal. It's not a traditional function call syntax. We are not writing emmy()
; we are just tagging the literal with the function. When this function is called, the first argument is an array of all the plain strings (the string between interpolated expressions). The second argument is the array where all the interpolated expressions are evaluated and stored.
Now what this means is that the tag function can actually change the resulting template tag:
function priceFilter(s, ...v){ //Bump up discount return s[0]+ (v[0] + 5); } let default_discount = 20; let greeting = priceFilter `Your purchase has a discount of ${default_discount} percent`; console.log(greeting); //Your purchase has a discount of 25
As you can see, we modified the value of the discount in the tag function and returned the modified values.
Maps and Sets
ES6 introduces four new data structures: Map, WeakMap, Set, and WeakSet. We discussed earlier that objects are the usual way of creating key-value pairs in JavaScript. The disadvantage of objects is that you cannot use non-string values as keys. The following snippets demonstrate how Maps are created in ES6:
let m = new Map(); let s = { 'seq' : 101 }; m.set('1','Albert'); m.set('MAX', 99); m.set(s,'Einstein'); console.log(m.has('1')); //true console.log(m.get(s)); //Einstein console.log(m.size); //3 m.delete(s); m.clear();
You can initialize the map while declaring it:
let m = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]);
If you want to iterate over the entries in the Map, you can use the entries()
function that will return you an iterator. You can iterate through all the keys using the keys()
function and you can iterate through the values of the Map using the values()
function:
let m2 = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]); for (let a of m2.entries()){ console.log(a); } //[1,"Albert"] [2,"Douglas"][3,"Clive"] for (let a of m2.keys()){ console.log(a); } //1 2 3 for (let a of m2.values()){ console.log(a); } //Albert Douglas Clive
A variation of JavaScript Maps is a WeakMap—a WeakMap does not prevent its keys from being garbage-collected. Keys for a WeakMap must be objects and the values can be arbitrary values. While a WeakMap behaves in the same way as a normal Map, you cannot iterate through it and you can't clear it. There are reasons behind these restrictions. As the state of the Map is not guaranteed to remain static (keys may get garbage-collected), you cannot ensure correct iteration.
There are not many cases where you may want to use WeakMap. Most uses of a Map can be written using normal Maps.
While Maps allow you to store arbitrary values, Sets are a collection of unique values. Sets have similar methods as Maps; however, set()
is replaced with add()
, and the get()
method does not exist. The reason that the get()
method is not there is because a Set has unique values, so you are interested in only checking whether the Set contains a value or not. Consider the following example:
let x = {'first': 'Albert'}; let s = new Set([1,2,'Sunday',x]); //console.log(s.has(x)); //true s.add(300); //console.log(s); //[1,2,"Sunday",{"first":"Albert"},300] for (let a of s.entries()){ console.log(a); } //[1,1] //[2,2] //["Sunday","Sunday"] //[{"first":"Albert"},{"first":"Albert"}] //[300,300] for (let a of s.keys()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300 for (let a of s.values()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300
The keys()
and values()
iterators both return a list of the unique values in the Set. The entries()
iterator yields a list of entry arrays, where both items of the array are the unique Set values. The default iterator for a Set is its values()
iterator.
Symbols
ES6 introduces a new data type called Symbol. A Symbol is guaranteed to be unique and immutable. Symbols are usually used as an identifier for object properties. They can be considered as uniquely generated IDs. You can create Symbols with the Symbol()
factory method—remember that this is not a constructor and hence you should not use a new
operator:
let s = Symbol(); console.log(typeof s); //symbol
Unlike strings, Symbols are guaranteed to be unique and hence help in preventing name clashes. With Symbols, we have an extensibility mechanism that works for everyone. ES6 comes with a number of predefined built-in Symbols that expose various meta behaviors on JavaScript object values.
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Block scoping
We discussed earlier that the variables in JavaScript are function-scoped. Variables created in a nested scope are available to the entire function. Several programming languages provide you with a default block scope where any variable declared within a block of code (usually delimited by {}
) is scoped (available) only within this block. To achieve a similar block scope in JavaScript, a prevalent method is to use
immediately-invoked function expressions (IIFE). Consider the following example:
var a = 1; (function blockscope(){ var a = 2; console.log(a); // 2 })(); console.log(a); // 1
Using the IIFE, we are creating a block scope for the a
variable. When a variable is declared in the IIFE, its scope is restricted within the function. This is the traditional way of simulating the block scope. ES6 supports block scoping without using IIFEs. In ES6, you can enclose any statement(s) in a block defined by {}
. Instead of using var
, you can declare a variable using let
to define the block scope. The preceding example can be rewritten using ES6 block scopes as follows:
"use strict"; var a = 1; { let a = 2; console.log( a ); // 2 } console.log( a ); // 1
Using standalone brackets {}
may seem unusual in JavaScript, but this convention is fairly common to create a block scope in many languages. The block scope kicks in other constructs such as if { }
or for (){ }
as well.
When you use a block scope in this way, it is generally preferred to put the variable declaration on top of the block. One difference between variables declared using var
and let
is that variables declared with var
are attached to the entire function scope, while variables declared using let
are attached to the block scope and they are not initialized until they appear in the block. Hence, you cannot access a variable declared with let
earlier than its declaration, whereas with variables declared using var
, the ordering doesn't matter:
function fooey() { console.log(foo); // ReferenceError let foo = 5000; }
One specific use of let
is in for loops. When we use a variable declared using var
in a for loop, it is created in the global or parent scope. We can create a block-scoped variable in the for loop scope by declaring a variable using let
. Consider the following example:
for (let i = 0; i<5; i++) { console.log(i); } console.log(i); // i is not defined
As i
is created using let
, it is scoped in the for
loop. You can see that the variable is not available outside the scope.
One more use of block scopes in ES6 is the ability to create constants. Using the const
keyword, you can create constants in the block scope. Once the value is set, you cannot change the value of such a constant:
if(true){ const a=1; console.log(a); a=100; ///"a" is read-only, you will get a TypeError }
A constant has to be initialized while being declared. The same block scope rules apply to functions also. When a function is declared inside a block, it is available only within that scope.
Default parameters
Defaulting is very common. You always set some default value to parameters passed to a function or variables that you initialize. You may have seen code similar to the following:
function sum(a,b){ a = a || 0; b = b || 0; return (a+b); } console.log(sum(9,9)); //18 console.log(sum(9)); //9
Here, we are using ||
(the OR operator) to default variables a
and b
to 0
if no value was supplied when this function was invoked. With ES6, you have a standard way of defaulting function arguments. The preceding example can be rewritten as follows:
function sum(a=0, b=0){ return (a+b); } console.log(sum(9,9)); //18 console.log(sum(9)); //9
You can pass any valid expression or function call as part of the default parameter list.
Spread and rest
ES6 has a new operator, …
. Based on how it is used, it is called either spread
or rest
. Let's look at a trivial example:
function print(a, b){ console.log(a,b); } print(...[1,2]); //1,2
What's happening here is that when you add …
before an array (or an iterable) it spreads the element of the array in individual variables in the function parameters. The a
and b
function parameters were assigned two values from the array when it was spread out. Extra parameters are ignored while spreading an array:
print(...[1,2,3 ]); //1,2
This would still print 1
and 2
because there are only two functional parameters available. Spreads can be used in other places also, such as array assignments:
var a = [1,2]; var b = [ 0, ...a, 3 ]; console.log( b ); //[0,1,2,3]
There is another use of the …
operator that is the very opposite of the one that we just saw. Instead of spreading the values, the same operator can gather them into one:
function print (a,...b){ console.log(a,b); } console.log(print(1,2,3,4,5,6,7)); //1 [2,3,4,5,6,7]
In this case, the variable b
takes the rest of the values. The a
variable took the first value as 1
and b
took the rest of the values as an array.
Destructuring
If you have worked on a functional language such as Erlang, you will relate to the concept of pattern matching. Destructuring in JavaScript is something very similar. Destructuring allows you to bind values to variables using pattern matching. Consider the following example:
var [start, end] = [0,5];
for (let i=start; i<end; i++){
console.log(i);
}
//prints - 0,1,2,3,4
We are assigning two variables with the help of array destructuring:
var [start, end] = [0,5];
As shown in the preceding example, we want the pattern to match when the first value is assigned to the first variable (start
) and the second value is assigned to the second variable (end
). Consider the following snippet to see how the destructuring of array elements works:
function fn() { return [1,2,3]; } var [a,b,c]=fn(); console.log(a,b,c); //1 2 3 //We can skip one of them var [d,,f]=fn(); console.log(d,f); //1 3 //Rest of the values are not used var [e,] = fn(); console.log(e); //1
Let's discuss how objects' destructuring works. Let's say that you have a function f
that returns an object as follows:
function f() { return { a: 'a', b: 'b', c: 'c' }; }
When we destructure the object being returned by this function, we can use the similar syntax as we saw earlier; the difference is that we use {}
instead of []
:
var { a: a, b: b, c: c } = f(); console.log(a,b,c); //a b c
Similar to arrays, we use pattern matching to assign variables to their corresponding values returned by the function. There is an even shorter way of writing this if you are using the same variable as the one being matched. The following example would do just fine:
var { a,b,c } = f();
However, you would mostly be using a different variable name from the one being returned by the function. It is important to remember that the syntax is source: destination and not the usual destination: source. Carefully observe the following example:
//this is target: source - which is incorrect var { x: a, x: b, x: c } = f(); console.log(x,y,z); //x is undefined, y is undefined z is undefined //this is source: target - correct var { a: x, b: y, c: z } = f(); console.log(x,y,z); // a b c
This is the opposite of the target = source way of assigning values and hence will take some time in getting used to.
Object literals
Object literals are everywhere in JavaScript. You would think that there is no scope of improvement there. However, ES6 wants to improve this too. ES6 introduces several shortcuts to create a concise syntax around object literals:
var firstname = "Albert", lastname = "Einstein", person = { firstname: firstname, lastname: lastname };
If you intend to use the same property name as the variable that you are assigning, you can use the concise property notation of ES6:
var firstname = "Albert", lastname = "Einstein", person = { firstname, lastname };
Similarly, you are assigning functions to properties as follows:
var person = { getName: function(){ // .. }, getAge: function(){ //.. } }
Instead of the preceding lines, you can say the following:
var person = { getName(){ // .. }, getAge(){ //.. } }
Template literals
I am sure you have done things such as the following:
function SuperLogger(level, clazz, msg){ console.log(level+": Exception happened in class:"+clazz+" - Exception :"+ msg); }
This is a very common way of replacing variable values to form a string literal. ES6 provides you with a new type of string literal using the backtick (`
) delimiter. You can use string interpolation to put placeholders in a template string literal. The placeholders will be parsed and evaluated.
The preceding example can be rewritten as follows:
function SuperLogger(level, clazz, msg){ console.log(`${level} : Exception happened in class: ${clazz} - Exception : {$msg}`); }
We are using ``
around a string literal. Within this literal, any expression of the ${..}
form is parsed immediately. This parsing is called interpolation. While parsing, the variable's value replaces the placeholder within ${}
. The resulting string is just a normal string with the placeholders replaced with actual variable values.
With string interpolation, you can split a string into multiple lines also, as shown in the following code (very similar to Python):
var quote = `Good night, good night! Parting is such sweet sorrow, that I shall say good night till it be morrow.`; console.log( quote );
You can use function calls or valid JavaScript expressions as part of the string interpolation:
function sum(a,b){ console.log(`The sum seems to be ${a + b}`); } sum(1,2); //The sum seems to be 3
The final variation of the template strings is called tagged template string. The idea is to modify the template string using a function. Consider the following example:
function emmy(key, ...values){
console.log(key);
console.log(values);
}
let category="Best Movie";
let movie="Adventures in ES6";
emmy`And the award for ${category} goes to ${movie}`;
//["And the award for "," goes to ",""]
//["Best Movie","Adventures in ES6"]
The strangest part is when we call the emmy
function with the template literal. It's not a traditional function call syntax. We are not writing emmy()
; we are just tagging the literal with the function. When this function is called, the first argument is an array of all the plain strings (the string between interpolated expressions). The second argument is the array where all the interpolated expressions are evaluated and stored.
Now what this means is that the tag function can actually change the resulting template tag:
function priceFilter(s, ...v){ //Bump up discount return s[0]+ (v[0] + 5); } let default_discount = 20; let greeting = priceFilter `Your purchase has a discount of ${default_discount} percent`; console.log(greeting); //Your purchase has a discount of 25
As you can see, we modified the value of the discount in the tag function and returned the modified values.
Maps and Sets
ES6 introduces four new data structures: Map, WeakMap, Set, and WeakSet. We discussed earlier that objects are the usual way of creating key-value pairs in JavaScript. The disadvantage of objects is that you cannot use non-string values as keys. The following snippets demonstrate how Maps are created in ES6:
let m = new Map(); let s = { 'seq' : 101 }; m.set('1','Albert'); m.set('MAX', 99); m.set(s,'Einstein'); console.log(m.has('1')); //true console.log(m.get(s)); //Einstein console.log(m.size); //3 m.delete(s); m.clear();
You can initialize the map while declaring it:
let m = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]);
If you want to iterate over the entries in the Map, you can use the entries()
function that will return you an iterator. You can iterate through all the keys using the keys()
function and you can iterate through the values of the Map using the values()
function:
let m2 = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]); for (let a of m2.entries()){ console.log(a); } //[1,"Albert"] [2,"Douglas"][3,"Clive"] for (let a of m2.keys()){ console.log(a); } //1 2 3 for (let a of m2.values()){ console.log(a); } //Albert Douglas Clive
A variation of JavaScript Maps is a WeakMap—a WeakMap does not prevent its keys from being garbage-collected. Keys for a WeakMap must be objects and the values can be arbitrary values. While a WeakMap behaves in the same way as a normal Map, you cannot iterate through it and you can't clear it. There are reasons behind these restrictions. As the state of the Map is not guaranteed to remain static (keys may get garbage-collected), you cannot ensure correct iteration.
There are not many cases where you may want to use WeakMap. Most uses of a Map can be written using normal Maps.
While Maps allow you to store arbitrary values, Sets are a collection of unique values. Sets have similar methods as Maps; however, set()
is replaced with add()
, and the get()
method does not exist. The reason that the get()
method is not there is because a Set has unique values, so you are interested in only checking whether the Set contains a value or not. Consider the following example:
let x = {'first': 'Albert'}; let s = new Set([1,2,'Sunday',x]); //console.log(s.has(x)); //true s.add(300); //console.log(s); //[1,2,"Sunday",{"first":"Albert"},300] for (let a of s.entries()){ console.log(a); } //[1,1] //[2,2] //["Sunday","Sunday"] //[{"first":"Albert"},{"first":"Albert"}] //[300,300] for (let a of s.keys()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300 for (let a of s.values()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300
The keys()
and values()
iterators both return a list of the unique values in the Set. The entries()
iterator yields a list of entry arrays, where both items of the array are the unique Set values. The default iterator for a Set is its values()
iterator.
Symbols
ES6 introduces a new data type called Symbol. A Symbol is guaranteed to be unique and immutable. Symbols are usually used as an identifier for object properties. They can be considered as uniquely generated IDs. You can create Symbols with the Symbol()
factory method—remember that this is not a constructor and hence you should not use a new
operator:
let s = Symbol(); console.log(typeof s); //symbol
Unlike strings, Symbols are guaranteed to be unique and hence help in preventing name clashes. With Symbols, we have an extensibility mechanism that works for everyone. ES6 comes with a number of predefined built-in Symbols that expose various meta behaviors on JavaScript object values.
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Default parameters
Defaulting is very common. You always set some default value to parameters passed to a function or variables that you initialize. You may have seen code similar to the following:
function sum(a,b){ a = a || 0; b = b || 0; return (a+b); } console.log(sum(9,9)); //18 console.log(sum(9)); //9
Here, we are using ||
(the OR operator) to default variables a
and b
to 0
if no value was supplied when this function was invoked. With ES6, you have a standard way of defaulting function arguments. The preceding example can be rewritten as follows:
function sum(a=0, b=0){ return (a+b); } console.log(sum(9,9)); //18 console.log(sum(9)); //9
You can pass any valid expression or function call as part of the default parameter list.
Spread and rest
ES6 has a new operator, …
. Based on how it is used, it is called either spread
or rest
. Let's look at a trivial example:
function print(a, b){ console.log(a,b); } print(...[1,2]); //1,2
What's happening here is that when you add …
before an array (or an iterable) it spreads the element of the array in individual variables in the function parameters. The a
and b
function parameters were assigned two values from the array when it was spread out. Extra parameters are ignored while spreading an array:
print(...[1,2,3 ]); //1,2
This would still print 1
and 2
because there are only two functional parameters available. Spreads can be used in other places also, such as array assignments:
var a = [1,2]; var b = [ 0, ...a, 3 ]; console.log( b ); //[0,1,2,3]
There is another use of the …
operator that is the very opposite of the one that we just saw. Instead of spreading the values, the same operator can gather them into one:
function print (a,...b){ console.log(a,b); } console.log(print(1,2,3,4,5,6,7)); //1 [2,3,4,5,6,7]
In this case, the variable b
takes the rest of the values. The a
variable took the first value as 1
and b
took the rest of the values as an array.
Destructuring
If you have worked on a functional language such as Erlang, you will relate to the concept of pattern matching. Destructuring in JavaScript is something very similar. Destructuring allows you to bind values to variables using pattern matching. Consider the following example:
var [start, end] = [0,5];
for (let i=start; i<end; i++){
console.log(i);
}
//prints - 0,1,2,3,4
We are assigning two variables with the help of array destructuring:
var [start, end] = [0,5];
As shown in the preceding example, we want the pattern to match when the first value is assigned to the first variable (start
) and the second value is assigned to the second variable (end
). Consider the following snippet to see how the destructuring of array elements works:
function fn() { return [1,2,3]; } var [a,b,c]=fn(); console.log(a,b,c); //1 2 3 //We can skip one of them var [d,,f]=fn(); console.log(d,f); //1 3 //Rest of the values are not used var [e,] = fn(); console.log(e); //1
Let's discuss how objects' destructuring works. Let's say that you have a function f
that returns an object as follows:
function f() { return { a: 'a', b: 'b', c: 'c' }; }
When we destructure the object being returned by this function, we can use the similar syntax as we saw earlier; the difference is that we use {}
instead of []
:
var { a: a, b: b, c: c } = f(); console.log(a,b,c); //a b c
Similar to arrays, we use pattern matching to assign variables to their corresponding values returned by the function. There is an even shorter way of writing this if you are using the same variable as the one being matched. The following example would do just fine:
var { a,b,c } = f();
However, you would mostly be using a different variable name from the one being returned by the function. It is important to remember that the syntax is source: destination and not the usual destination: source. Carefully observe the following example:
//this is target: source - which is incorrect var { x: a, x: b, x: c } = f(); console.log(x,y,z); //x is undefined, y is undefined z is undefined //this is source: target - correct var { a: x, b: y, c: z } = f(); console.log(x,y,z); // a b c
This is the opposite of the target = source way of assigning values and hence will take some time in getting used to.
Object literals
Object literals are everywhere in JavaScript. You would think that there is no scope of improvement there. However, ES6 wants to improve this too. ES6 introduces several shortcuts to create a concise syntax around object literals:
var firstname = "Albert", lastname = "Einstein", person = { firstname: firstname, lastname: lastname };
If you intend to use the same property name as the variable that you are assigning, you can use the concise property notation of ES6:
var firstname = "Albert", lastname = "Einstein", person = { firstname, lastname };
Similarly, you are assigning functions to properties as follows:
var person = { getName: function(){ // .. }, getAge: function(){ //.. } }
Instead of the preceding lines, you can say the following:
var person = { getName(){ // .. }, getAge(){ //.. } }
Template literals
I am sure you have done things such as the following:
function SuperLogger(level, clazz, msg){ console.log(level+": Exception happened in class:"+clazz+" - Exception :"+ msg); }
This is a very common way of replacing variable values to form a string literal. ES6 provides you with a new type of string literal using the backtick (`
) delimiter. You can use string interpolation to put placeholders in a template string literal. The placeholders will be parsed and evaluated.
The preceding example can be rewritten as follows:
function SuperLogger(level, clazz, msg){ console.log(`${level} : Exception happened in class: ${clazz} - Exception : {$msg}`); }
We are using ``
around a string literal. Within this literal, any expression of the ${..}
form is parsed immediately. This parsing is called interpolation. While parsing, the variable's value replaces the placeholder within ${}
. The resulting string is just a normal string with the placeholders replaced with actual variable values.
With string interpolation, you can split a string into multiple lines also, as shown in the following code (very similar to Python):
var quote = `Good night, good night! Parting is such sweet sorrow, that I shall say good night till it be morrow.`; console.log( quote );
You can use function calls or valid JavaScript expressions as part of the string interpolation:
function sum(a,b){ console.log(`The sum seems to be ${a + b}`); } sum(1,2); //The sum seems to be 3
The final variation of the template strings is called tagged template string. The idea is to modify the template string using a function. Consider the following example:
function emmy(key, ...values){
console.log(key);
console.log(values);
}
let category="Best Movie";
let movie="Adventures in ES6";
emmy`And the award for ${category} goes to ${movie}`;
//["And the award for "," goes to ",""]
//["Best Movie","Adventures in ES6"]
The strangest part is when we call the emmy
function with the template literal. It's not a traditional function call syntax. We are not writing emmy()
; we are just tagging the literal with the function. When this function is called, the first argument is an array of all the plain strings (the string between interpolated expressions). The second argument is the array where all the interpolated expressions are evaluated and stored.
Now what this means is that the tag function can actually change the resulting template tag:
function priceFilter(s, ...v){ //Bump up discount return s[0]+ (v[0] + 5); } let default_discount = 20; let greeting = priceFilter `Your purchase has a discount of ${default_discount} percent`; console.log(greeting); //Your purchase has a discount of 25
As you can see, we modified the value of the discount in the tag function and returned the modified values.
Maps and Sets
ES6 introduces four new data structures: Map, WeakMap, Set, and WeakSet. We discussed earlier that objects are the usual way of creating key-value pairs in JavaScript. The disadvantage of objects is that you cannot use non-string values as keys. The following snippets demonstrate how Maps are created in ES6:
let m = new Map(); let s = { 'seq' : 101 }; m.set('1','Albert'); m.set('MAX', 99); m.set(s,'Einstein'); console.log(m.has('1')); //true console.log(m.get(s)); //Einstein console.log(m.size); //3 m.delete(s); m.clear();
You can initialize the map while declaring it:
let m = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]);
If you want to iterate over the entries in the Map, you can use the entries()
function that will return you an iterator. You can iterate through all the keys using the keys()
function and you can iterate through the values of the Map using the values()
function:
let m2 = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]); for (let a of m2.entries()){ console.log(a); } //[1,"Albert"] [2,"Douglas"][3,"Clive"] for (let a of m2.keys()){ console.log(a); } //1 2 3 for (let a of m2.values()){ console.log(a); } //Albert Douglas Clive
A variation of JavaScript Maps is a WeakMap—a WeakMap does not prevent its keys from being garbage-collected. Keys for a WeakMap must be objects and the values can be arbitrary values. While a WeakMap behaves in the same way as a normal Map, you cannot iterate through it and you can't clear it. There are reasons behind these restrictions. As the state of the Map is not guaranteed to remain static (keys may get garbage-collected), you cannot ensure correct iteration.
There are not many cases where you may want to use WeakMap. Most uses of a Map can be written using normal Maps.
While Maps allow you to store arbitrary values, Sets are a collection of unique values. Sets have similar methods as Maps; however, set()
is replaced with add()
, and the get()
method does not exist. The reason that the get()
method is not there is because a Set has unique values, so you are interested in only checking whether the Set contains a value or not. Consider the following example:
let x = {'first': 'Albert'}; let s = new Set([1,2,'Sunday',x]); //console.log(s.has(x)); //true s.add(300); //console.log(s); //[1,2,"Sunday",{"first":"Albert"},300] for (let a of s.entries()){ console.log(a); } //[1,1] //[2,2] //["Sunday","Sunday"] //[{"first":"Albert"},{"first":"Albert"}] //[300,300] for (let a of s.keys()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300 for (let a of s.values()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300
The keys()
and values()
iterators both return a list of the unique values in the Set. The entries()
iterator yields a list of entry arrays, where both items of the array are the unique Set values. The default iterator for a Set is its values()
iterator.
Symbols
ES6 introduces a new data type called Symbol. A Symbol is guaranteed to be unique and immutable. Symbols are usually used as an identifier for object properties. They can be considered as uniquely generated IDs. You can create Symbols with the Symbol()
factory method—remember that this is not a constructor and hence you should not use a new
operator:
let s = Symbol(); console.log(typeof s); //symbol
Unlike strings, Symbols are guaranteed to be unique and hence help in preventing name clashes. With Symbols, we have an extensibility mechanism that works for everyone. ES6 comes with a number of predefined built-in Symbols that expose various meta behaviors on JavaScript object values.
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Spread and rest
ES6 has a new operator, …
. Based on how it is used, it is called either spread
or rest
. Let's look at a trivial example:
function print(a, b){ console.log(a,b); } print(...[1,2]); //1,2
What's happening here is that when you add …
before an array (or an iterable) it spreads the element of the array in individual variables in the function parameters. The a
and b
function parameters were assigned two values from the array when it was spread out. Extra parameters are ignored while spreading an array:
print(...[1,2,3 ]); //1,2
This would still print 1
and 2
because there are only two functional parameters available. Spreads can be used in other places also, such as array assignments:
var a = [1,2]; var b = [ 0, ...a, 3 ]; console.log( b ); //[0,1,2,3]
There is another use of the …
operator that is the very opposite of the one that we just saw. Instead of spreading the values, the same operator can gather them into one:
function print (a,...b){ console.log(a,b); } console.log(print(1,2,3,4,5,6,7)); //1 [2,3,4,5,6,7]
In this case, the variable b
takes the rest of the values. The a
variable took the first value as 1
and b
took the rest of the values as an array.
Destructuring
If you have worked on a functional language such as Erlang, you will relate to the concept of pattern matching. Destructuring in JavaScript is something very similar. Destructuring allows you to bind values to variables using pattern matching. Consider the following example:
var [start, end] = [0,5];
for (let i=start; i<end; i++){
console.log(i);
}
//prints - 0,1,2,3,4
We are assigning two variables with the help of array destructuring:
var [start, end] = [0,5];
As shown in the preceding example, we want the pattern to match when the first value is assigned to the first variable (start
) and the second value is assigned to the second variable (end
). Consider the following snippet to see how the destructuring of array elements works:
function fn() { return [1,2,3]; } var [a,b,c]=fn(); console.log(a,b,c); //1 2 3 //We can skip one of them var [d,,f]=fn(); console.log(d,f); //1 3 //Rest of the values are not used var [e,] = fn(); console.log(e); //1
Let's discuss how objects' destructuring works. Let's say that you have a function f
that returns an object as follows:
function f() { return { a: 'a', b: 'b', c: 'c' }; }
When we destructure the object being returned by this function, we can use the similar syntax as we saw earlier; the difference is that we use {}
instead of []
:
var { a: a, b: b, c: c } = f(); console.log(a,b,c); //a b c
Similar to arrays, we use pattern matching to assign variables to their corresponding values returned by the function. There is an even shorter way of writing this if you are using the same variable as the one being matched. The following example would do just fine:
var { a,b,c } = f();
However, you would mostly be using a different variable name from the one being returned by the function. It is important to remember that the syntax is source: destination and not the usual destination: source. Carefully observe the following example:
//this is target: source - which is incorrect var { x: a, x: b, x: c } = f(); console.log(x,y,z); //x is undefined, y is undefined z is undefined //this is source: target - correct var { a: x, b: y, c: z } = f(); console.log(x,y,z); // a b c
This is the opposite of the target = source way of assigning values and hence will take some time in getting used to.
Object literals
Object literals are everywhere in JavaScript. You would think that there is no scope of improvement there. However, ES6 wants to improve this too. ES6 introduces several shortcuts to create a concise syntax around object literals:
var firstname = "Albert", lastname = "Einstein", person = { firstname: firstname, lastname: lastname };
If you intend to use the same property name as the variable that you are assigning, you can use the concise property notation of ES6:
var firstname = "Albert", lastname = "Einstein", person = { firstname, lastname };
Similarly, you are assigning functions to properties as follows:
var person = { getName: function(){ // .. }, getAge: function(){ //.. } }
Instead of the preceding lines, you can say the following:
var person = { getName(){ // .. }, getAge(){ //.. } }
Template literals
I am sure you have done things such as the following:
function SuperLogger(level, clazz, msg){ console.log(level+": Exception happened in class:"+clazz+" - Exception :"+ msg); }
This is a very common way of replacing variable values to form a string literal. ES6 provides you with a new type of string literal using the backtick (`
) delimiter. You can use string interpolation to put placeholders in a template string literal. The placeholders will be parsed and evaluated.
The preceding example can be rewritten as follows:
function SuperLogger(level, clazz, msg){ console.log(`${level} : Exception happened in class: ${clazz} - Exception : {$msg}`); }
We are using ``
around a string literal. Within this literal, any expression of the ${..}
form is parsed immediately. This parsing is called interpolation. While parsing, the variable's value replaces the placeholder within ${}
. The resulting string is just a normal string with the placeholders replaced with actual variable values.
With string interpolation, you can split a string into multiple lines also, as shown in the following code (very similar to Python):
var quote = `Good night, good night! Parting is such sweet sorrow, that I shall say good night till it be morrow.`; console.log( quote );
You can use function calls or valid JavaScript expressions as part of the string interpolation:
function sum(a,b){ console.log(`The sum seems to be ${a + b}`); } sum(1,2); //The sum seems to be 3
The final variation of the template strings is called tagged template string. The idea is to modify the template string using a function. Consider the following example:
function emmy(key, ...values){
console.log(key);
console.log(values);
}
let category="Best Movie";
let movie="Adventures in ES6";
emmy`And the award for ${category} goes to ${movie}`;
//["And the award for "," goes to ",""]
//["Best Movie","Adventures in ES6"]
The strangest part is when we call the emmy
function with the template literal. It's not a traditional function call syntax. We are not writing emmy()
; we are just tagging the literal with the function. When this function is called, the first argument is an array of all the plain strings (the string between interpolated expressions). The second argument is the array where all the interpolated expressions are evaluated and stored.
Now what this means is that the tag function can actually change the resulting template tag:
function priceFilter(s, ...v){ //Bump up discount return s[0]+ (v[0] + 5); } let default_discount = 20; let greeting = priceFilter `Your purchase has a discount of ${default_discount} percent`; console.log(greeting); //Your purchase has a discount of 25
As you can see, we modified the value of the discount in the tag function and returned the modified values.
Maps and Sets
ES6 introduces four new data structures: Map, WeakMap, Set, and WeakSet. We discussed earlier that objects are the usual way of creating key-value pairs in JavaScript. The disadvantage of objects is that you cannot use non-string values as keys. The following snippets demonstrate how Maps are created in ES6:
let m = new Map(); let s = { 'seq' : 101 }; m.set('1','Albert'); m.set('MAX', 99); m.set(s,'Einstein'); console.log(m.has('1')); //true console.log(m.get(s)); //Einstein console.log(m.size); //3 m.delete(s); m.clear();
You can initialize the map while declaring it:
let m = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]);
If you want to iterate over the entries in the Map, you can use the entries()
function that will return you an iterator. You can iterate through all the keys using the keys()
function and you can iterate through the values of the Map using the values()
function:
let m2 = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]); for (let a of m2.entries()){ console.log(a); } //[1,"Albert"] [2,"Douglas"][3,"Clive"] for (let a of m2.keys()){ console.log(a); } //1 2 3 for (let a of m2.values()){ console.log(a); } //Albert Douglas Clive
A variation of JavaScript Maps is a WeakMap—a WeakMap does not prevent its keys from being garbage-collected. Keys for a WeakMap must be objects and the values can be arbitrary values. While a WeakMap behaves in the same way as a normal Map, you cannot iterate through it and you can't clear it. There are reasons behind these restrictions. As the state of the Map is not guaranteed to remain static (keys may get garbage-collected), you cannot ensure correct iteration.
There are not many cases where you may want to use WeakMap. Most uses of a Map can be written using normal Maps.
While Maps allow you to store arbitrary values, Sets are a collection of unique values. Sets have similar methods as Maps; however, set()
is replaced with add()
, and the get()
method does not exist. The reason that the get()
method is not there is because a Set has unique values, so you are interested in only checking whether the Set contains a value or not. Consider the following example:
let x = {'first': 'Albert'}; let s = new Set([1,2,'Sunday',x]); //console.log(s.has(x)); //true s.add(300); //console.log(s); //[1,2,"Sunday",{"first":"Albert"},300] for (let a of s.entries()){ console.log(a); } //[1,1] //[2,2] //["Sunday","Sunday"] //[{"first":"Albert"},{"first":"Albert"}] //[300,300] for (let a of s.keys()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300 for (let a of s.values()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300
The keys()
and values()
iterators both return a list of the unique values in the Set. The entries()
iterator yields a list of entry arrays, where both items of the array are the unique Set values. The default iterator for a Set is its values()
iterator.
Symbols
ES6 introduces a new data type called Symbol. A Symbol is guaranteed to be unique and immutable. Symbols are usually used as an identifier for object properties. They can be considered as uniquely generated IDs. You can create Symbols with the Symbol()
factory method—remember that this is not a constructor and hence you should not use a new
operator:
let s = Symbol(); console.log(typeof s); //symbol
Unlike strings, Symbols are guaranteed to be unique and hence help in preventing name clashes. With Symbols, we have an extensibility mechanism that works for everyone. ES6 comes with a number of predefined built-in Symbols that expose various meta behaviors on JavaScript object values.
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Destructuring
If you have worked on a functional language such as Erlang, you will relate to the concept of pattern matching. Destructuring in JavaScript is something very similar. Destructuring allows you to bind values to variables using pattern matching. Consider the following example:
var [start, end] = [0,5];
for (let i=start; i<end; i++){
console.log(i);
}
//prints - 0,1,2,3,4
We are assigning two variables with the help of array destructuring:
var [start, end] = [0,5];
As shown in the preceding example, we want the pattern to match when the first value is assigned to the first variable (start
) and the second value is assigned to the second variable (end
). Consider the following snippet to see how the destructuring of array elements works:
function fn() { return [1,2,3]; } var [a,b,c]=fn(); console.log(a,b,c); //1 2 3 //We can skip one of them var [d,,f]=fn(); console.log(d,f); //1 3 //Rest of the values are not used var [e,] = fn(); console.log(e); //1
Let's discuss how objects' destructuring works. Let's say that you have a function f
that returns an object as follows:
function f() { return { a: 'a', b: 'b', c: 'c' }; }
When we destructure the object being returned by this function, we can use the similar syntax as we saw earlier; the difference is that we use {}
instead of []
:
var { a: a, b: b, c: c } = f(); console.log(a,b,c); //a b c
Similar to arrays, we use pattern matching to assign variables to their corresponding values returned by the function. There is an even shorter way of writing this if you are using the same variable as the one being matched. The following example would do just fine:
var { a,b,c } = f();
However, you would mostly be using a different variable name from the one being returned by the function. It is important to remember that the syntax is source: destination and not the usual destination: source. Carefully observe the following example:
//this is target: source - which is incorrect var { x: a, x: b, x: c } = f(); console.log(x,y,z); //x is undefined, y is undefined z is undefined //this is source: target - correct var { a: x, b: y, c: z } = f(); console.log(x,y,z); // a b c
This is the opposite of the target = source way of assigning values and hence will take some time in getting used to.
Object literals
Object literals are everywhere in JavaScript. You would think that there is no scope of improvement there. However, ES6 wants to improve this too. ES6 introduces several shortcuts to create a concise syntax around object literals:
var firstname = "Albert", lastname = "Einstein", person = { firstname: firstname, lastname: lastname };
If you intend to use the same property name as the variable that you are assigning, you can use the concise property notation of ES6:
var firstname = "Albert", lastname = "Einstein", person = { firstname, lastname };
Similarly, you are assigning functions to properties as follows:
var person = { getName: function(){ // .. }, getAge: function(){ //.. } }
Instead of the preceding lines, you can say the following:
var person = { getName(){ // .. }, getAge(){ //.. } }
Template literals
I am sure you have done things such as the following:
function SuperLogger(level, clazz, msg){ console.log(level+": Exception happened in class:"+clazz+" - Exception :"+ msg); }
This is a very common way of replacing variable values to form a string literal. ES6 provides you with a new type of string literal using the backtick (`
) delimiter. You can use string interpolation to put placeholders in a template string literal. The placeholders will be parsed and evaluated.
The preceding example can be rewritten as follows:
function SuperLogger(level, clazz, msg){ console.log(`${level} : Exception happened in class: ${clazz} - Exception : {$msg}`); }
We are using ``
around a string literal. Within this literal, any expression of the ${..}
form is parsed immediately. This parsing is called interpolation. While parsing, the variable's value replaces the placeholder within ${}
. The resulting string is just a normal string with the placeholders replaced with actual variable values.
With string interpolation, you can split a string into multiple lines also, as shown in the following code (very similar to Python):
var quote = `Good night, good night! Parting is such sweet sorrow, that I shall say good night till it be morrow.`; console.log( quote );
You can use function calls or valid JavaScript expressions as part of the string interpolation:
function sum(a,b){ console.log(`The sum seems to be ${a + b}`); } sum(1,2); //The sum seems to be 3
The final variation of the template strings is called tagged template string. The idea is to modify the template string using a function. Consider the following example:
function emmy(key, ...values){
console.log(key);
console.log(values);
}
let category="Best Movie";
let movie="Adventures in ES6";
emmy`And the award for ${category} goes to ${movie}`;
//["And the award for "," goes to ",""]
//["Best Movie","Adventures in ES6"]
The strangest part is when we call the emmy
function with the template literal. It's not a traditional function call syntax. We are not writing emmy()
; we are just tagging the literal with the function. When this function is called, the first argument is an array of all the plain strings (the string between interpolated expressions). The second argument is the array where all the interpolated expressions are evaluated and stored.
Now what this means is that the tag function can actually change the resulting template tag:
function priceFilter(s, ...v){ //Bump up discount return s[0]+ (v[0] + 5); } let default_discount = 20; let greeting = priceFilter `Your purchase has a discount of ${default_discount} percent`; console.log(greeting); //Your purchase has a discount of 25
As you can see, we modified the value of the discount in the tag function and returned the modified values.
Maps and Sets
ES6 introduces four new data structures: Map, WeakMap, Set, and WeakSet. We discussed earlier that objects are the usual way of creating key-value pairs in JavaScript. The disadvantage of objects is that you cannot use non-string values as keys. The following snippets demonstrate how Maps are created in ES6:
let m = new Map(); let s = { 'seq' : 101 }; m.set('1','Albert'); m.set('MAX', 99); m.set(s,'Einstein'); console.log(m.has('1')); //true console.log(m.get(s)); //Einstein console.log(m.size); //3 m.delete(s); m.clear();
You can initialize the map while declaring it:
let m = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]);
If you want to iterate over the entries in the Map, you can use the entries()
function that will return you an iterator. You can iterate through all the keys using the keys()
function and you can iterate through the values of the Map using the values()
function:
let m2 = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]); for (let a of m2.entries()){ console.log(a); } //[1,"Albert"] [2,"Douglas"][3,"Clive"] for (let a of m2.keys()){ console.log(a); } //1 2 3 for (let a of m2.values()){ console.log(a); } //Albert Douglas Clive
A variation of JavaScript Maps is a WeakMap—a WeakMap does not prevent its keys from being garbage-collected. Keys for a WeakMap must be objects and the values can be arbitrary values. While a WeakMap behaves in the same way as a normal Map, you cannot iterate through it and you can't clear it. There are reasons behind these restrictions. As the state of the Map is not guaranteed to remain static (keys may get garbage-collected), you cannot ensure correct iteration.
There are not many cases where you may want to use WeakMap. Most uses of a Map can be written using normal Maps.
While Maps allow you to store arbitrary values, Sets are a collection of unique values. Sets have similar methods as Maps; however, set()
is replaced with add()
, and the get()
method does not exist. The reason that the get()
method is not there is because a Set has unique values, so you are interested in only checking whether the Set contains a value or not. Consider the following example:
let x = {'first': 'Albert'}; let s = new Set([1,2,'Sunday',x]); //console.log(s.has(x)); //true s.add(300); //console.log(s); //[1,2,"Sunday",{"first":"Albert"},300] for (let a of s.entries()){ console.log(a); } //[1,1] //[2,2] //["Sunday","Sunday"] //[{"first":"Albert"},{"first":"Albert"}] //[300,300] for (let a of s.keys()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300 for (let a of s.values()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300
The keys()
and values()
iterators both return a list of the unique values in the Set. The entries()
iterator yields a list of entry arrays, where both items of the array are the unique Set values. The default iterator for a Set is its values()
iterator.
Symbols
ES6 introduces a new data type called Symbol. A Symbol is guaranteed to be unique and immutable. Symbols are usually used as an identifier for object properties. They can be considered as uniquely generated IDs. You can create Symbols with the Symbol()
factory method—remember that this is not a constructor and hence you should not use a new
operator:
let s = Symbol(); console.log(typeof s); //symbol
Unlike strings, Symbols are guaranteed to be unique and hence help in preventing name clashes. With Symbols, we have an extensibility mechanism that works for everyone. ES6 comes with a number of predefined built-in Symbols that expose various meta behaviors on JavaScript object values.
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Object literals
Object literals are everywhere in JavaScript. You would think that there is no scope of improvement there. However, ES6 wants to improve this too. ES6 introduces several shortcuts to create a concise syntax around object literals:
var firstname = "Albert", lastname = "Einstein", person = { firstname: firstname, lastname: lastname };
If you intend to use the same property name as the variable that you are assigning, you can use the concise property notation of ES6:
var firstname = "Albert", lastname = "Einstein", person = { firstname, lastname };
Similarly, you are assigning functions to properties as follows:
var person = { getName: function(){ // .. }, getAge: function(){ //.. } }
Instead of the preceding lines, you can say the following:
var person = { getName(){ // .. }, getAge(){ //.. } }
Template literals
I am sure you have done things such as the following:
function SuperLogger(level, clazz, msg){ console.log(level+": Exception happened in class:"+clazz+" - Exception :"+ msg); }
This is a very common way of replacing variable values to form a string literal. ES6 provides you with a new type of string literal using the backtick (`
) delimiter. You can use string interpolation to put placeholders in a template string literal. The placeholders will be parsed and evaluated.
The preceding example can be rewritten as follows:
function SuperLogger(level, clazz, msg){ console.log(`${level} : Exception happened in class: ${clazz} - Exception : {$msg}`); }
We are using ``
around a string literal. Within this literal, any expression of the ${..}
form is parsed immediately. This parsing is called interpolation. While parsing, the variable's value replaces the placeholder within ${}
. The resulting string is just a normal string with the placeholders replaced with actual variable values.
With string interpolation, you can split a string into multiple lines also, as shown in the following code (very similar to Python):
var quote = `Good night, good night! Parting is such sweet sorrow, that I shall say good night till it be morrow.`; console.log( quote );
You can use function calls or valid JavaScript expressions as part of the string interpolation:
function sum(a,b){ console.log(`The sum seems to be ${a + b}`); } sum(1,2); //The sum seems to be 3
The final variation of the template strings is called tagged template string. The idea is to modify the template string using a function. Consider the following example:
function emmy(key, ...values){
console.log(key);
console.log(values);
}
let category="Best Movie";
let movie="Adventures in ES6";
emmy`And the award for ${category} goes to ${movie}`;
//["And the award for "," goes to ",""]
//["Best Movie","Adventures in ES6"]
The strangest part is when we call the emmy
function with the template literal. It's not a traditional function call syntax. We are not writing emmy()
; we are just tagging the literal with the function. When this function is called, the first argument is an array of all the plain strings (the string between interpolated expressions). The second argument is the array where all the interpolated expressions are evaluated and stored.
Now what this means is that the tag function can actually change the resulting template tag:
function priceFilter(s, ...v){ //Bump up discount return s[0]+ (v[0] + 5); } let default_discount = 20; let greeting = priceFilter `Your purchase has a discount of ${default_discount} percent`; console.log(greeting); //Your purchase has a discount of 25
As you can see, we modified the value of the discount in the tag function and returned the modified values.
Maps and Sets
ES6 introduces four new data structures: Map, WeakMap, Set, and WeakSet. We discussed earlier that objects are the usual way of creating key-value pairs in JavaScript. The disadvantage of objects is that you cannot use non-string values as keys. The following snippets demonstrate how Maps are created in ES6:
let m = new Map(); let s = { 'seq' : 101 }; m.set('1','Albert'); m.set('MAX', 99); m.set(s,'Einstein'); console.log(m.has('1')); //true console.log(m.get(s)); //Einstein console.log(m.size); //3 m.delete(s); m.clear();
You can initialize the map while declaring it:
let m = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]);
If you want to iterate over the entries in the Map, you can use the entries()
function that will return you an iterator. You can iterate through all the keys using the keys()
function and you can iterate through the values of the Map using the values()
function:
let m2 = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]); for (let a of m2.entries()){ console.log(a); } //[1,"Albert"] [2,"Douglas"][3,"Clive"] for (let a of m2.keys()){ console.log(a); } //1 2 3 for (let a of m2.values()){ console.log(a); } //Albert Douglas Clive
A variation of JavaScript Maps is a WeakMap—a WeakMap does not prevent its keys from being garbage-collected. Keys for a WeakMap must be objects and the values can be arbitrary values. While a WeakMap behaves in the same way as a normal Map, you cannot iterate through it and you can't clear it. There are reasons behind these restrictions. As the state of the Map is not guaranteed to remain static (keys may get garbage-collected), you cannot ensure correct iteration.
There are not many cases where you may want to use WeakMap. Most uses of a Map can be written using normal Maps.
While Maps allow you to store arbitrary values, Sets are a collection of unique values. Sets have similar methods as Maps; however, set()
is replaced with add()
, and the get()
method does not exist. The reason that the get()
method is not there is because a Set has unique values, so you are interested in only checking whether the Set contains a value or not. Consider the following example:
let x = {'first': 'Albert'}; let s = new Set([1,2,'Sunday',x]); //console.log(s.has(x)); //true s.add(300); //console.log(s); //[1,2,"Sunday",{"first":"Albert"},300] for (let a of s.entries()){ console.log(a); } //[1,1] //[2,2] //["Sunday","Sunday"] //[{"first":"Albert"},{"first":"Albert"}] //[300,300] for (let a of s.keys()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300 for (let a of s.values()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300
The keys()
and values()
iterators both return a list of the unique values in the Set. The entries()
iterator yields a list of entry arrays, where both items of the array are the unique Set values. The default iterator for a Set is its values()
iterator.
Symbols
ES6 introduces a new data type called Symbol. A Symbol is guaranteed to be unique and immutable. Symbols are usually used as an identifier for object properties. They can be considered as uniquely generated IDs. You can create Symbols with the Symbol()
factory method—remember that this is not a constructor and hence you should not use a new
operator:
let s = Symbol(); console.log(typeof s); //symbol
Unlike strings, Symbols are guaranteed to be unique and hence help in preventing name clashes. With Symbols, we have an extensibility mechanism that works for everyone. ES6 comes with a number of predefined built-in Symbols that expose various meta behaviors on JavaScript object values.
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Template literals
I am sure you have done things such as the following:
function SuperLogger(level, clazz, msg){ console.log(level+": Exception happened in class:"+clazz+" - Exception :"+ msg); }
This is a very common way of replacing variable values to form a string literal. ES6 provides you with a new type of string literal using the backtick (`
) delimiter. You can use string interpolation to put placeholders in a template string literal. The placeholders will be parsed and evaluated.
The preceding example can be rewritten as follows:
function SuperLogger(level, clazz, msg){ console.log(`${level} : Exception happened in class: ${clazz} - Exception : {$msg}`); }
We are using ``
around a string literal. Within this literal, any expression of the ${..}
form is parsed immediately. This parsing is called interpolation. While parsing, the variable's value replaces the placeholder within ${}
. The resulting string is just a normal string with the placeholders replaced with actual variable values.
With string interpolation, you can split a string into multiple lines also, as shown in the following code (very similar to Python):
var quote = `Good night, good night! Parting is such sweet sorrow, that I shall say good night till it be morrow.`; console.log( quote );
You can use function calls or valid JavaScript expressions as part of the string interpolation:
function sum(a,b){ console.log(`The sum seems to be ${a + b}`); } sum(1,2); //The sum seems to be 3
The final variation of the template strings is called tagged template string. The idea is to modify the template string using a function. Consider the following example:
function emmy(key, ...values){
console.log(key);
console.log(values);
}
let category="Best Movie";
let movie="Adventures in ES6";
emmy`And the award for ${category} goes to ${movie}`;
//["And the award for "," goes to ",""]
//["Best Movie","Adventures in ES6"]
The strangest part is when we call the emmy
function with the template literal. It's not a traditional function call syntax. We are not writing emmy()
; we are just tagging the literal with the function. When this function is called, the first argument is an array of all the plain strings (the string between interpolated expressions). The second argument is the array where all the interpolated expressions are evaluated and stored.
Now what this means is that the tag function can actually change the resulting template tag:
function priceFilter(s, ...v){ //Bump up discount return s[0]+ (v[0] + 5); } let default_discount = 20; let greeting = priceFilter `Your purchase has a discount of ${default_discount} percent`; console.log(greeting); //Your purchase has a discount of 25
As you can see, we modified the value of the discount in the tag function and returned the modified values.
Maps and Sets
ES6 introduces four new data structures: Map, WeakMap, Set, and WeakSet. We discussed earlier that objects are the usual way of creating key-value pairs in JavaScript. The disadvantage of objects is that you cannot use non-string values as keys. The following snippets demonstrate how Maps are created in ES6:
let m = new Map(); let s = { 'seq' : 101 }; m.set('1','Albert'); m.set('MAX', 99); m.set(s,'Einstein'); console.log(m.has('1')); //true console.log(m.get(s)); //Einstein console.log(m.size); //3 m.delete(s); m.clear();
You can initialize the map while declaring it:
let m = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]);
If you want to iterate over the entries in the Map, you can use the entries()
function that will return you an iterator. You can iterate through all the keys using the keys()
function and you can iterate through the values of the Map using the values()
function:
let m2 = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]); for (let a of m2.entries()){ console.log(a); } //[1,"Albert"] [2,"Douglas"][3,"Clive"] for (let a of m2.keys()){ console.log(a); } //1 2 3 for (let a of m2.values()){ console.log(a); } //Albert Douglas Clive
A variation of JavaScript Maps is a WeakMap—a WeakMap does not prevent its keys from being garbage-collected. Keys for a WeakMap must be objects and the values can be arbitrary values. While a WeakMap behaves in the same way as a normal Map, you cannot iterate through it and you can't clear it. There are reasons behind these restrictions. As the state of the Map is not guaranteed to remain static (keys may get garbage-collected), you cannot ensure correct iteration.
There are not many cases where you may want to use WeakMap. Most uses of a Map can be written using normal Maps.
While Maps allow you to store arbitrary values, Sets are a collection of unique values. Sets have similar methods as Maps; however, set()
is replaced with add()
, and the get()
method does not exist. The reason that the get()
method is not there is because a Set has unique values, so you are interested in only checking whether the Set contains a value or not. Consider the following example:
let x = {'first': 'Albert'}; let s = new Set([1,2,'Sunday',x]); //console.log(s.has(x)); //true s.add(300); //console.log(s); //[1,2,"Sunday",{"first":"Albert"},300] for (let a of s.entries()){ console.log(a); } //[1,1] //[2,2] //["Sunday","Sunday"] //[{"first":"Albert"},{"first":"Albert"}] //[300,300] for (let a of s.keys()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300 for (let a of s.values()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300
The keys()
and values()
iterators both return a list of the unique values in the Set. The entries()
iterator yields a list of entry arrays, where both items of the array are the unique Set values. The default iterator for a Set is its values()
iterator.
Symbols
ES6 introduces a new data type called Symbol. A Symbol is guaranteed to be unique and immutable. Symbols are usually used as an identifier for object properties. They can be considered as uniquely generated IDs. You can create Symbols with the Symbol()
factory method—remember that this is not a constructor and hence you should not use a new
operator:
let s = Symbol(); console.log(typeof s); //symbol
Unlike strings, Symbols are guaranteed to be unique and hence help in preventing name clashes. With Symbols, we have an extensibility mechanism that works for everyone. ES6 comes with a number of predefined built-in Symbols that expose various meta behaviors on JavaScript object values.
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Maps and Sets
ES6 introduces four new data structures: Map, WeakMap, Set, and WeakSet. We discussed earlier that objects are the usual way of creating key-value pairs in JavaScript. The disadvantage of objects is that you cannot use non-string values as keys. The following snippets demonstrate how Maps are created in ES6:
let m = new Map(); let s = { 'seq' : 101 }; m.set('1','Albert'); m.set('MAX', 99); m.set(s,'Einstein'); console.log(m.has('1')); //true console.log(m.get(s)); //Einstein console.log(m.size); //3 m.delete(s); m.clear();
You can initialize the map while declaring it:
let m = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]);
If you want to iterate over the entries in the Map, you can use the entries()
function that will return you an iterator. You can iterate through all the keys using the keys()
function and you can iterate through the values of the Map using the values()
function:
let m2 = new Map([ [ 1, 'Albert' ], [ 2, 'Douglas' ], [ 3, 'Clive' ], ]); for (let a of m2.entries()){ console.log(a); } //[1,"Albert"] [2,"Douglas"][3,"Clive"] for (let a of m2.keys()){ console.log(a); } //1 2 3 for (let a of m2.values()){ console.log(a); } //Albert Douglas Clive
A variation of JavaScript Maps is a WeakMap—a WeakMap does not prevent its keys from being garbage-collected. Keys for a WeakMap must be objects and the values can be arbitrary values. While a WeakMap behaves in the same way as a normal Map, you cannot iterate through it and you can't clear it. There are reasons behind these restrictions. As the state of the Map is not guaranteed to remain static (keys may get garbage-collected), you cannot ensure correct iteration.
There are not many cases where you may want to use WeakMap. Most uses of a Map can be written using normal Maps.
While Maps allow you to store arbitrary values, Sets are a collection of unique values. Sets have similar methods as Maps; however, set()
is replaced with add()
, and the get()
method does not exist. The reason that the get()
method is not there is because a Set has unique values, so you are interested in only checking whether the Set contains a value or not. Consider the following example:
let x = {'first': 'Albert'}; let s = new Set([1,2,'Sunday',x]); //console.log(s.has(x)); //true s.add(300); //console.log(s); //[1,2,"Sunday",{"first":"Albert"},300] for (let a of s.entries()){ console.log(a); } //[1,1] //[2,2] //["Sunday","Sunday"] //[{"first":"Albert"},{"first":"Albert"}] //[300,300] for (let a of s.keys()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300 for (let a of s.values()){ console.log(a); } //1 //2 //Sunday //{"first":"Albert"} //300
The keys()
and values()
iterators both return a list of the unique values in the Set. The entries()
iterator yields a list of entry arrays, where both items of the array are the unique Set values. The default iterator for a Set is its values()
iterator.
Symbols
ES6 introduces a new data type called Symbol. A Symbol is guaranteed to be unique and immutable. Symbols are usually used as an identifier for object properties. They can be considered as uniquely generated IDs. You can create Symbols with the Symbol()
factory method—remember that this is not a constructor and hence you should not use a new
operator:
let s = Symbol(); console.log(typeof s); //symbol
Unlike strings, Symbols are guaranteed to be unique and hence help in preventing name clashes. With Symbols, we have an extensibility mechanism that works for everyone. ES6 comes with a number of predefined built-in Symbols that expose various meta behaviors on JavaScript object values.
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Symbols
ES6 introduces a new data type called Symbol. A Symbol is guaranteed to be unique and immutable. Symbols are usually used as an identifier for object properties. They can be considered as uniquely generated IDs. You can create Symbols with the Symbol()
factory method—remember that this is not a constructor and hence you should not use a new
operator:
let s = Symbol(); console.log(typeof s); //symbol
Unlike strings, Symbols are guaranteed to be unique and hence help in preventing name clashes. With Symbols, we have an extensibility mechanism that works for everyone. ES6 comes with a number of predefined built-in Symbols that expose various meta behaviors on JavaScript object values.
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Iterators
Iterators have been around in other programming languages for quite some time. They give convenience methods to work with collections of data. ES6 introduces iterators for the same use case. ES6 iterators are objects with a specific interface. Iterators have a next()
method that returns an object. The returning object has two properties—value
(the next value) and done
(indicates whether the last result has been reached). ES6 also defines an Iterable
interface, which describes objects that must be able to produce iterators. Let's look at an array, which is an iterable, and the iterator that it can produce to consume its values:
var a = [1,2]; var i = a[Symbol.iterator](); console.log(i.next()); // { value: 1, done: false } console.log(i.next()); // { value: 2, done: false } console.log(i.next()); // { value: undefined, done: true }
As you can see, we are accessing the array's iterator via Symbol.iterator()
and calling the next()
method on it to get each successive element. Both value
and done
are returned by the next()
method call. When you call next()
past the last element in the array, you get an undefined value and done: true
, indicating that you have iterated over the entire array.
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
For..of loops
ES6 adds a new iteration mechanism in form of the for..of
loop, which loops over the set of values produced by an iterator.
The value that we iterate over with for..of
is an iterable.
Let's compare for..of
to for..in
:
var list = ['Sunday','Monday','Tuesday']; for (let i in list){ console.log(i); //0 1 2 } for (let i of list){ console.log(i); //Sunday Monday Tuesday }
As you can see, using the for..in
loop, you can iterate over indexes of the list
array, while the for..of
loop lets you iterate over the values stored in the list
array.
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Arrow functions
One of the most interesting new parts of ECMAScript 6 is arrow functions. Arrow functions are, as the name suggests, functions defined with a new syntax that uses an arrow (=>
) as part of the syntax. Let's first see how arrow functions look:
//Traditional Function function multiply(a,b) { return a*b; } //Arrow var multiply = (a,b) => a*b; console.log(multiply(1,2)); //2
The arrow function definition consists of a parameter list (of zero or more parameters and surrounding ( .. )
if there's not exactly one parameter), followed by the =>
marker, which is followed by a function body.
The body of the function can be enclosed by { .. }
if there's more than one expression in the body. If there's only one expression, and you omit the surrounding { .. }
, there's an implied return in front of the expression. There are several variations of how you can write arrow functions. The following are the most commonly used:
// single argument, single statement //arg => expression; var f1 = x => console.log("Just X"); f1(); //Just X // multiple arguments, single statement //(arg1 [, arg2]) => expression; var f2 = (x,y) => x*y; console.log(f2(2,2)); //4 // single argument, multiple statements // arg => { // statements; // } var f3 = x => { if(x>5){ console.log(x); } else { console.log(x+5); } } f3(6); //6 // multiple arguments, multiple statements // ([arg] [, arg]) => { // statements // } var f4 = (x,y) => { if(x!=0 && y!=0){ return x*y; } } console.log(f4(2,2));//4 // with no arguments, single statement //() => expression; var f5 = () => 2*2; console.log(f5()); //4 //IIFE console.log(( x => x * 3 )( 3 )); // 9
It is important to remember that all the characteristics of a normal function parameter are available to arrow functions, including default values, destructuring, and rest parameters.
Arrow functions offer a convenient and short syntax, which gives your code a very functional programming flavor. Arrow functions are popular because they offer an attractive promise of writing concise functions by dropping function, return, and { .. } from the code. However, arrow functions are designed to fundamentally solve a particular and common pain point with this-aware coding. In normal ES5 functions, every new function defined its own value of this
(a new object in case of a constructor, undefined
in strict mode function calls, context object if the function is called as an object method, and so on). JavaScript functions always have their own this
and this prevents you from accessing the this
of, for example, a surrounding method from inside a callback. To understand this problem, consider the following example:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ // --> 1 'use strict'; return s.map(function (a){ // --> 2 return this.str + a; // --> 3 }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //Cannot read property 'str' of undefined
On the line marked with 3
, we are trying to get this.str
, but the anonymous function also has its own this
, which shadows this
from the method from line 1
. To fix this in ES5, we can assign this
to a variable and use the variable instead:
function CustomStr(str){
this.str = str;
}
CustomStr.prototype.add = function(s){
'use strict';
var that = this; // --> 1
return s.map(function (a){ // --> 2
return that.str + a; // --> 3
});
};
var customStr = new CustomStr("Hello");
console.log(customStr.add(["World"]));
//["HelloWorld]
On the line marked with 1
, we are assigning this
to a variable, that
, and in the anonymous function we are using the that
variable, which will have a reference to this
from the correct context.
ES6 arrow functions have lexical this
, meaning that the arrow functions capture the this
value of the enclosing context. We can convert the preceding function to an equivalent arrow function as follows:
function CustomStr(str){ this.str = str; } CustomStr.prototype.add = function(s){ return s.map((a)=> { return this.str + a; }); }; var customStr = new CustomStr("Hello"); console.log(customStr.add(["World"])); //["HelloWorld]
Summary
In this chapter, we discussed a few important features being added to the language in ES6. It's an exciting collection of new language features and paradigms and, using polyfills and transpilers, you can start with them right away. JavaScript is an ever growing language and it is important to understand what the future holds. ES6 features make JavaScript an even more interesting and mature language. In the next chapter, we will dive deep into manipulating the browser's Document Object Model (DOM) and events using JavaScript with jQuery.