Learning ECMAScript 6

4.5 (16 reviews total)
By Narayan Prusty
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies

About this book

ECMAScript 6 is the new edition to the ECMAScript language, whose specifications are inherited by JavaScript. ES6 gives a vast makeover to JavaScript by adding new syntaxes and APIs to write complex applications and libraries that are easier to debug and maintain. By learning the latest version of ECMAScript, you'll have a greater understanding of JavaScript and more confidence and fluency when developing with it - follow this book and use and adopt ES6 features into your work, instead of the usual tired JavaScript hacks and tricks.

The book begins by introducing ECMAScript 6's built-in objects and  shows you how to create custom Iterators.  It also provides you with guidance on Next, how to write asynchronous code in a synchronous style using ES6, so you can unlock greater control and sophistication in the way you develop with JavaScript.

Beyond this, you will also learn how to use Reflect API to inspect and manipulate object properties. Next, it teaches how to create proxies, and use it to intercept and customize operations performed on objects. Finally, it explains old modular programming techniques such as IIFE, CommonJS, AMD, and UMD and also compares it with ECMAScript modules and demonstrates how modules can increase the performance of websites when used.

Publication date:
August 2015
Publisher
Packt
Pages
202
ISBN
9781785884443

 

Chapter 1. Playing with Syntax

JavaScript was lacking behind some other programming languages when compared to various syntactic forms such as declaring constant variables, declaring block scoped variables, extracting data from arrays, shorter syntax for declaring functions and so on. ES6 adds up a lot of new syntax-based features to JavaScript, which helps the developers to write less and do more. ES6 also prevents programmers from using various hacks for achieving various goals, which have negative performance impact and made code harder to read. In this chapter, we will look at the new syntactic features, introduced by ES6.

In this chapter, we'll cover:

  • Creating the block scoped variables using the let keyword

  • Creating constant variables using the const keyword

  • The spread operator and the rest parameter

  • Extracting the data from iterables and objects using the destructuring assignment

  • The arrow functions

  • The new syntaxes for creating the object properties

 

The let keyword


The ES6 let keyword is used to declare a block scoped variable, optionally initializing it to a value. The programmers who come from other programming language background, but new to JavaScript, often end up writing error-prone JavaScript programs, believing that the JavaScript variables are block scoped. Almost every popular programming language has the same rules when it comes to the variable scopes, but JavaScript acts a bit different due to a lack of the block scoped variables. Due to the fact that JavaScript variables are not block scoped, there are chances of memory leak and also the JavaScript programs are harder to read and debug.

Declaring function scoped variables

The JavaScript variables that are declared using the var keyword are called as function scoped variables. The function scoped variables are accessible globally to the script, that is, throughout the script, if declared outside a function. Similarly, if the function scoped variables are declared inside a function, then they become accessible throughout the function, but not outside the function.

Here is an example that shows how to create the function-scoped variables:

var a = 12; //accessible globally

function myFunction()
{
  console.log(a);

  var b = 13; //accessible throughout function

  if(true)
  {
    var c = 14; //accessible throughout function
    console.log(b);
  }

  console.log(c);
}

myFunction();

The output of the code is:

12
13
14

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

Here, you can see that the c variable is accessible outside the if statement, but this is not the case in other programming languages. So, the programmers coming from other languages would expect the c variable to be undefined outside the if statement, but that's not the case. Therefore, ES6 had introduced the let keyword, which can be used for creating variables that are block scoped.

Declaring block scoped variables

Variables that are declared using the let keyword are called as block scoped variables. The block scoped variables behave the same way as the function scoped variables when declared outside a function, that is, they are accessible globally. But when the block scoped variables are declared inside a block, then they are accessible inside the block that they are defined in (and also any sub-blocks) but not outside the block.

Note

A block is used to group zero or more statements. A pair of curly brackets delimits the block, that is {}.

Let's take the previous example script, replace var with the let keyword, and see the output:

let a = 12; //accessible globally

function myFunction()
{
  console.log(a);

  let b = 13; //accessible throughout function

  if(true)
  {
    let c = 14; //accessible throughout the "if" statement
    console.log(b);
  }

  console.log(c);
}

myFunction();

The output of the code is:

12
13
Reference Error Exception

Now, the output is as expected by a programmer who is used to another programming language.

Re-declaring variables

When you declare a variable using the var keyword that is already declared using var keyword (in the same scope) then it's overwritten. Consider this example:

var a = 0;
var a = 1;

console.log(a);

function myFunction()
{
  var b = 2;
  var b = 3;

  console.log(b);
}

myFunction();

The output of the code is:

1
3

The output is as expected. But the variables created using the let keyword don't behave in the same way.

When you declare a variable using the let keyword that is already declared using the let keyword in the same scope, then it throws a TypeError exception. Consider this example:

let a = 0;
let a = 1; //TypeError

function myFunction()
{
  let b = 2;
  let b = 3; //TypeError

  if(true)
  {
    let c = 4;
    let c = 5; //TypeError
  }
}

myFunction();

When you declare a variable with a name that's already accessible in a function (or inner function), or is a sub-block using var or the let keyword respectively, then it's a different variable. Here, is an example that shows the behavior:

var a = 1;
let b = 2;

function myFunction()
{
  var a = 3; //different variable
  let b = 4; //different variable

  if(true)
  {
    var a = 5; //overwritten
    let b = 6; //different variable

    console.log(a);
    console.log(b);
  }

  console.log(a);
  console.log(b);
}

myFunction();

console.log(a);
console.log(b);

The output of the code is:

5
6
5
4
1
2

Note

var versus let, which one to use?

When writing the ES6 code, it is recommended to switch to using the let keyword because it makes scripts more memory friendly, prevents scoping mistakes, prevents accidental bugs, and makes the code easier to read. But if you are already addicted to the var keyword and comfortable using it, then you can still use this.

You may be wondering why not just make the var keyword to define the block-scoped variables instead of introducing the let keyword? The reason why the var keyword wasn't made enough to define block-scoped variables, instead of introducing the let keyword, was for the sake of backward compatibility.

 

The const keyword


The ES6 const keyword is used to declare the read-only variables, that is, the variables whose value cannot be reassigned. Before ES6, the programmers usually used to prefix the variables that were supposed to be constant. For example, take a look at the following code:

var const_pi = 3.141;
var r = 2;
console.log(const_pi * r * r); //Output "12.564"

The value of pi should always remain constant. Here, although we have prefixed it, there is still a chance that we might accidentally change its value somewhere in the program, as they're no native protection to the value of pi. Prefixing is just not enough to keep the track of the constant variables.

Therefore, the const keyword was introduced to provide a native protection to the constant variables. So, the previous program should be written in this way in ES6:

const pi = 3.141;
var r = 2;

console.log(pi * r * r); //Output "12.564"

pi = 12; //throws read-only exception

Here, when we tried to change the value of pi, a read-only exception was thrown.

The scope of constant variables

Constant variables are block-scoped variables, that is, they follow the same scoping rules as the variables that are declared using the let keyword. Here is an example, which shows the scope of the constant variables:

const a = 12; //accessible globally

function myFunction()
{
  console.log(a);

  const b = 13; //accessible throughout function

  if(true)
  {
    const c = 14; //accessible throughout the "if" statement
    console.log(b);
  }

  console.log(c);
}

myFunction();

The output of the preceding code is:

12
13
ReferenceError Exception

Here, we can see that the constant variables behave in the same way as the block scoped variables, when it comes to the scoping rules.

Referencing the objects using constant variables

When we assign an object to a variable, the reference of the object is what the variable holds and not the object itself. So, when assigning an object to a constant variable, the reference of the object becomes constant to that variable and not to the object itself. Therefore, the object is mutable.

Consider this example:

const a = {
  "name" : "John"
};

console.log(a.name);

a.name = "Eden";

console.log(a.name);

a = {}; //throws read-only exception

The output of the preceding code is:

John
Eden
a is read only: Exception

In this example, the a variable stores the address (that is, reference) of the object. So the address of the object is the value of the a variable, and it cannot be changed. But the object is mutable. So when we tried to assign another object to the a variable, we got an exception as we were trying to change the value of the a variable.

 

Default parameter values


In JavaScript there is no defined way to assign the default values to the function parameters that are not passed. So, the programmers usually check for the parameters with the undefined value (as it is the default value for the missing parameters), and assign the default values to them. Here is an example, which shows how to do this:

function myFunction(x, y, z)
{
  x = x === undefined ? 1 : x;
  y = y === undefined ? 2 : y;
  z = z === undefined ? 3 : z;

  console.log(x, y, z); //Output "6 7 3"
}
myFunction(6, 7);

ES6 provides a new syntax that can be used to do this in an easier way. Here is the code which demonstrates how to do this in ES6:

function myFunction(x = 1, y = 2, z = 3)
{
  console.log(x, y, z); // Output "6 7 3"
}

myFunction(6,7);

Also, passing undefined is considered as missing an argument. Here is an example to demonstrate this:

function myFunction(x = 1, y = 2, z = 3)
{
  console.log(x, y, z); // Output "1 7 9"
}

myFunction(undefined,7,9);

Defaults can also be expressions. Here is an example to demonstrate this:

function myFunction(x = 1, y = 2, z = 3 + 5)
{
  console.log(x, y, z); // Output "6 7 8"
}

myFunction(6,7);
 

The spread operator


A spread operator is represented by the "…" token. A spread operator splits an iterable object into the individual values.

Note

An iterable is an object that contains a group of values, and implements ES6 iterable protocol to let us iterate through its values. An array is an example of built in an iterable object.

A spread operator can be placed wherever multiple function arguments or multiple elements (for array literals) are expected in code.

The spread operator is commonly used to spread the values of an iterable object into the arguments of a function. Let's take an example of an array and see how to split it into the arguments of a function.

Before ES6, for providing the values of an array as function argument, the programmers used the apply() method of functions. Here is an example:

function myFunction(a, b)
{
  return a + b;
}

var data = [1, 4];
var result = myFunction.apply(null, data);

console.log(result); //Output "5"

Here, the apply method takes an array, extracts the values, passes them as individual arguments to the function, and then calls it.

ES6 provides an easy way to do this, using the spread operator. Here is an example:

function myFunction(a, b)
{
 return a + b;
}

let data = [1, 4];
let result = myFunction(...data);
console.log(result); //Output "5"

During runtime, before the JavaScript interpreter calls the myFunction function, it replaces …data with the 1,4 expression:

let result = myFunction(...data);

The previous code is replaced with:

let result = myFunction(1,4);

After this, the function is called.

Note

A spread operator doesn't call the apply() method. The JavaScript runtime engine spreads the array using the iteration protocols, and has nothing to do with the apply() method, but the behavior is same.

Other usages of the spread operator

The spread operator is not just limited to spreading an iterable object into the function arguments, but it can be used wherever multiple elements (for array literals) are expected in code. So it has many uses. Let's see some other use cases of the spread operator for arrays.

Making array values a part of another array

It can also be used to make the array values a part of another array. Here is an example code that demonstrates how to make the values of an existing array a part of another array while creating it.

let array1 = [2,3,4];
let array2 = [1, ...array1, 5, 6, 7];

console.log(array2); //Output "1, 2, 3, 4, 5, 6, 7"

Here the following line:

let array2 = [1, ...array1, 5, 6, 7];

Will be replaced with the following line:

let array2 = [1, 2, 3, 4, 5, 6, 7];

Pushing the values of an array into another array

Sometimes, we may need to push the values of an existing array into the end of another existing array.

Before ES6, this is how the programmers used to do it:

var array1 = [2,3,4];
var array2 = [1];

Array.prototype.push.apply(array2, array1);

console.log(array2); //Output "1, 2, 3, 4"

But in ES6 we have a much cleaner way to do it, which is as follows:

let array1 = [2,3,4];
let array2 = [1];

array2.push(...array1);

console.log(array2); //Output "1, 2, 3, 4"

Here the push method takes a series of variables, and adds them to the end of the array on which it is called.

Here the following line:

array2.push(...array1);

Will be replaced with the following line:

array2.push(2, 3, 4);

Spreading multiple arrays

Multiple arrays can be spread on a single line of expression. For example, take the following code:

let array1 = [1];
let array2 = [2];
let array3 = [...array1, ...array2, ...[3, 4]];//multi array spread
let array4 = [5];

function myFunction(a, b, c, d, e)
{
  return a+b+c+d+e;
}

let result = myFunction(...array3, ...array4); //multi array spread

console.log(result); //Output "15"
 

The rest parameter


The rest parameter is also represented by the "…" token. The last parameter of a function prefixed with "…" is called as a rest parameter. The rest parameter is an array type, which contains the rest of the parameters of a function when number of arguments exceeds the number of named parameters.

The rest parameter is used to capture a variable number of the function arguments from within a function.

Before ES6, the programmers used the arguments object of a function to retrieve the extra arguments, passed to the function. The arguments object is not an array, but it provides some interfaces that are similar to an array.

Here is a code example that shows how to use the arguments object to retrieve the extra arguments:

function myFunction(a, b)
{
  var args = Array.prototype.slice.call(arguments, myFunction.length);

  console.log(args);
}

myFunction(1, 2, 3, 4, 5); //Output "3, 4, 5"

In ES6, this can be done in a much easier and cleaner way, using the rest parameter. Here is an example of using the rest parameter:

function myFunction(a, b, ...args)
{
  console.log(args); //Output "3, 4, 5"
}

myFunction(1, 2, 3, 4, 5);

The arguments object is not an array object. Therefore, to do array operations on the arguments object, you need to first convert it to an array. As the ES6 rest parameter is an array type, it's easier to work with it.

Note

What is the "…" token called?

The "…" token is called as the spread operator or rest parameter, depending on where and how it's used.

 

The destructuring assignment


The destructuring assignment is an expression that allows you to assign the values or properties of an iterable or object, to the variables, using a syntax that looks similar to the array or object construction literals respectively.

A destructuring assignment makes it easy to extract data from iterables or objects by providing a shorter syntax. A destructuring assignment is already present in the programming languages, such as Perl and Python, and works the same way everywhere.

There are two kinds of destructuring assignment expressions—the array and object destructuring assignment. Let's see each of them in details.

The array destructuring assignment

An array destructuring assignment is used to extract the values of an iterable object and assign them to the variables. It's named as the array destructuring assignment because the expression is similar to an array construction literal.

Before ES6, the programmers used to do it this way to assign the values of an array to the variables:

var myArray = [1, 2, 3];
var a = myArray[0];
var b = myArray[1];
var c = myArray[2];

Here, we are extracting the values of an array and assigning them to the a, b, c variables respectively.

In ES6, we can do this in just one line statement using the array destructuring assignment:

let myArray = [1, 2, 3];
let a, b, c;

[a, b, c] = myArray; //array destructuring assignment syntax

As you see, the [a, b, c] is the array destructuring expression.

On the left-hand side of the array destructuring statement, we need to place the variables to which we want to assign the array values, using syntax similar to an array literal. On right-hand side, we need to place an array (actually any iterable object) whose values we want to extract.

The previous example code can be made even shorter in this way:

let [a, b, c] = [1, 2, 3];

Here we create the variables on the same statement and instead of providing the array variable, we provide the array with a construction literal.

If there are fewer variables than items in the array, then only the first items are considered.

Note

If you place a non-iterable object on the right-hand side of the array destructuring assignment syntax, then a TypeError exception is thrown.

Ignoring values

We can also ignore some of the values of the iterable. Here is example code, which shows how to do this:

let [a, , b] = [1, 2, 3];

console.log(a);
console.log(b);

The output is as follows:

1
3

Using the rest operator in the array destructuring assignment

We can prefix the last variable of the array destructuring expression using the "…" token. In this case, the variable is always converted into an array object, which holds the rest of the values of the iterable object, if the number of other variables is less than the values in the iterable object.

Consider this example to understand it:

let [a, ...b] = [1, 2, 3, 4, 5, 6];

console.log(a);
console.log(Array.isArray(b));
console.log(b);

The output is as follows:

1
true
2,3,4,5,6

In the previous example code, you can see that the b variable is converted into an array, and it holds all the other values of the right-hand side array.

Here the "…" token is called as the rest operator.

We can also ignore the values while using the rest operator. Here is an example to demonstrate this:

let [a, , ,...b] = [1, 2, 3, 4, 5, 6];

console.log(a);
console.log(b);

The output is as follows:

1
4,5,6

Here, we ignored the 2, 3 values.

Default values for variables

While destructuring, you can also provide the default values to the variables if an array index is undefined. Here is an example to demonstrate this:

let [a, b, c = 3] = [1, 2];
console.log(c); //Output "3"

Nested array destructuring

We can also extract the values from a multi-dimensional array and assign them to variables. Here is an example to demonstrate this:

let [a, b, [c, d]] = [1, 2, [3, 4]];

Using the destructuring assignment as a parameter

We can also use the array destructuring expression as the function parameter for extracting the values of an iterable object, passed as argument into the function parameters. Here is an example to demonstrate this:

function myFunction([a, b, c = 3])
{
  console.log(a, b, c); //Output "1 2 3"
}

myFunction([1, 2]);

Earlier in this chapter, we saw that if we pass undefined as an argument to a function call, then JavaScript checks for the default parameter value. So, we can provide a default array here too, which will be used if the argument is undefined. Here is an example to demonstrate this:

function myFunction([a, b, c = 3] = [1, 2, 3])
{
  console.log(a, b, c);  //Output "1 2 3"
}

myFunction(undefined);

Here, we passed undefined as an argument and therefore, the default array, which is [1, 2, 3], was used for extracting the values.

The object destructuring assignment

An object destructuring assignment is used to the extract property values of an object and assign them to the variables.

Before ES6, the programmers used to do it in the following way to assign the values of an object's properties to the variables:

var object = {"name" : "John", "age" : 23};
var name = object.name;
var age = object.age;

In ES6, we can do this in just one line statement, using the object destructuring assignment:

let object = {"name" : "John", "age" : 23};
let name, age;

({name, age} = object); //object destructuring assignment syntax

On the left-hand side of the object destructuring statement, we need to place the variables to which we want to assign the object property values using syntax similar to object literal. On right-hand side, we need to place an object whose property values we want to extract are finally close the statement using the ( ) token.

Here the variable names must be same as the object property names. If you want to assign different variable names, then you can do it this way:

let object = {"name" : "John", "age" : 23};
let x, y;

({name: x, age: y} = object);

The previous code can be made even shorter this way:

let {name: x, age: y} = {"name" : "John", "age" : 23};

Here we are create the variables and object on the same line. We don't need to close the statement using the ( ) token, as we are creating the variables on the same statement.

Default values for the variables

You can also provide the default values to the variables, if the object property is undefined while destructuring. Here is an example to demonstrate this:

let {a, b, c = 3} = {a: "1", b: "2"};
console.log(c); //Output "3"

Destructuring computed property names

Some property names are constructed dynamically using expressions. In this case, to extract the property values, we can use the [ ] token to provide the property name an expression. Here is an example:

let {["first"+"Name"]: x} = { firstName: "Eden" };
console.log(x); //Output "Eden"

Destructuring nested objects

We can also the extract property values from the nested objects, that is, the objects within the objects. Here is an example to demonstrate this:

var {name, otherInfo: {age}} = {name: "Eden", otherInfo: {age: 23}};
console.log(name, age); //Eden 23

Using the object destructuring assignment as a parameter

Just like the array destructuring assignment, we can also use the object destructuring assignment as a function parameter. Here is an example to demonstrate this:

functionmyFunction({name = 'Eden', age = 23, profession = "Designer"} = {})
{
  console.log(name, age, profession); //Output "John 23 Designer"
}

myFunction({name: "John", age: 23});

Here, we passed an empty object as a default parameter value, which will be used as a default object if undefined is passed as a function argument.

 

The arrow functions


ES6 provides a new way to create functions using the => operator. These functions are called as arrow functions. This new method has a shorter syntax, and the arrow functions are the anonymous functions.

Here is an example that shows how to create an arrow function:

let circleArea = (pi, r) => {
  let area = pi * r * r;
  return area;
}

let result = circleArea(3.14, 3);

console.log(result); //Output "28.26"

Here, circleArea is a variable, referencing to the anonymous arrow function. The previous code is similar to the next code in ES5:

Var circleArea = function(pi, r) {
  var area = pi * r * r;
  return area;
}

var result = circleArea(3.14, 3);

console.log(result); //Output "28.26"

If an arrow function contains just one statement, then you don't have to use the {} brackets to wrap the code. Here is an example:

let circleArea = (pi, r) => pi * r * r;
let result = circleArea(3.14, 3);

console.log(result); //Output "28.26"

When {} brackets are not used then the value of the statement in the body is automatically returned.

The value of "this" in an arrow function

In the arrow functions, the value of this keyword is same as the value of this keyword of the enclosing scope (the global or function scope, inside whichever the arrow function is defined), instead of referring to the context object (that is, the object inside of which the function is a property), which is the value of this in traditional functions.

Consider this example to understand the difference in the traditional function's and the arrow function's this value:

var object = {
  f1: function(){
    console.log(this);
    var f2 = function(){ console.log(this); }
    f2();
    setTimeout(f2, 1000);
  }
}

object.f1();

The output is as follows:

Object
Window
Window

Here, this inside the f1 function refers to object, as f1 is the property of it. this inside f2 refers to the window object, as f2 is a property of the window object.

But this behaves differently in the arrow functions. Let's replace the traditional functions with the arrow functions in the preceding code and see the value of this:

var object = {
  f1: () => {
    console.log(this);
    var f2 = () => { console.log(this); }
    f2();
    setTimeout(f2, 1000);
  }
}

object.f1();

The output is as follows:

Window
Window
Window

Here, this inside the f1 function copies the this value of global scope, as f1 lies in global scope. this inside f2 copies the this value of f1, as f2 lies in the f1 scope.

Other differences between the arrow and traditional functions

The arrow functions cannot be used as object constructors that is, the new operator cannot be applied on them.

Apart from syntax, the this value, and the new operator, everything else is the same between the arrow and traditional functions, that is, they both are the instances of the Function constructor.

 

The enhanced object literals


ES6 has added some new syntax-based extensions to the {} object literal for creating properties. Let's see them:

Defining properties

ES6 provides a shorter syntax for assigning the object properties to the values of the variables, which have the same name as the properties.

In ES5, you have been doing this:

var x = 1, y = 2;
var object = {
  x: x,
  y: y
};

console.log(object.x); //output "1"

In ES6, you can do it this way:

let x = 1, y = 2;
let object = { x, y };

console.log(object.x); //output "1"

Defining methods

ES6 provides a new syntax for defining the methods on an object. Here is an example to demonstrate the new syntax:

let object = {
  myFunction(){
    console.log("Hello World!!!"); //Output "Hello World!!!"
  }
}

object.myFunction();

This concise function allows the use of super in them, whereas the traditional methods of the objects don't allow the use of super. We will learn more about it later in this book.

The computed property names

The property names that are evaluated during runtime are called as the computed property names. An expression is usually resolved to find the property name dynamically.

In ES5, the computed properties are defined in this way:

var object = {};

object["first"+"Name"] = "Eden";//"firstName" is the property name

//extract
console.log(object["first"+"Name"]); //Output "Eden"

Here, after creating the object, we attach the properties to the object. But in ES6, we can add the properties with the computed name while creating the objects. Here is an example:

let object = {
  ["first" + "Name"]: "Eden",
};

//extract
console.log(object["first" + "Name"]); //Output "Eden"
 

Summary


In this chapter, we learned about the variable's scopes, read-only variables, splitting arrays into individual values, passing indefinite parameters to a function, extracting data from objects and arrays, arrow functions, and new syntaxes for creating object properties.

In the next chapter, we will learn about built-in objects and symbols, and we will discover the properties added by ES6 into strings, arrays, and objects.

About the Author

  • Narayan Prusty

    Narayan Prusty is a full-stack developer. He works as a consultant for various start-ups around the world. He has worked on various technologies and programming languages but is very passionate about JavaScript, WordPress, Ethereum, Solr, React, Cordova, MongoDB, and AWS. Apart from consulting for various start-ups, he also runs a blog titled QNimate and a video tutorial site titled QScutter, where he shares information about a lot of the technologies he works on.

    Browse publications by this author

Latest Reviews

(16 reviews total)
Todo bien, muhas gracias!
Excellent and so effective
Good