JS ES6 Features

What is ECMAScript 6 (or ES6)

ECMAScript 2015 (or ES6) is the sixth major edition of the ECMAScript language specification, which defines the standard for JavaScript implementation.

ES6 introduced significant changes to JavaScript, including block-scoped variables, a new loop for iterating over arrays and objects, template literals, and various enhancements aimed at improving JavaScript programming.

The let Keyword

ES6 introduces the let keyword for variable declaration, alongside the existing var keyword. The main difference lies in how they handle scope.

Variables declared with var are function-scoped and hoisted to the top of their scope. On the other hand, variables declared with let are block-scoped within curly braces {} and are not hoisted.

Block scoping means that a variable declared with let inside a loop or a block of code exists only within that block and is not accessible outside of it, as shown in the example below:

// ES6 syntax
for(let i = 0; i < 5; i++) {
console.log(i); // 0,1,2,3,4
}
console.log(i); // undefined


// ES5 syntax
for(var i = 0; i < 5; i++) {
console.log(i); // 0,1,2,3,4
}
console.log(i); // 5

In the example above, notice that the variable i declared in the first block is not accessible outside the for loop. This block-scoping feature allows us to reuse the same variable name within different blocks, leading to fewer variable declarations and cleaner code.


The const Keyword

The const keyword introduced in ES6 allows the definition of constants in JavaScript. Constants are read-only, meaning once they are assigned a value, they cannot be reassigned. Like variables declared with let, constants are also block-scoped.

const PI = 3.14;
console.log(PI); // 3.14

PI = 10; // error

However, you can still modify properties of objects or elements within arrays:

// Changing object property value
const PERSON = {name: "Peter", age: 28};
console.log(PERSON.age); // 28
PERSON.age = 30;
console.log(PERSON.age); // 30

// Changing array element
const COLORS = ["red", "green", "blue"];
console.log(COLORS[0]); // red
COLORS[0] = "yellow";
console.log(COLORS[0]); // yellow

The for...of Loop

The for...of loop is a new feature that simplifies iterating over arrays or other iterable objects. It executes the loop body for each element in the iterable. Here's an example:

// Iterating over array
let letters = ["a", "b", "c", "d", "e", "f"];

for(let letter of letters) {
console.log(letter); // a,b,c,d,e,f
}

// Iterating over string
let greet = "Hello World!";

for(let character of greet) {
console.log(character); // H,e,l,l,o, ,W,o,r,l,d,!
}

The for...of loop doesn't function with objects because objects are not iterable. To iterate over an object's properties, you should use the for-in loop.


Template Literals

Template literals offer a straightforward and neat way to create multi-line strings and perform string interpolation. They allow embedding variables or expressions into a string effortlessly.

Template literals use back-ticks (` `) instead of traditional double or single quotes. Variables or expressions can be inserted within the string using the ${...} syntax. Compare the following examples to appreciate their usefulness:

// Simple multi-line string
let str = `The quick brown fox
jumps over the lazy dog.`;

// String with embedded variables and expression
let a = 10;
let b = 20;
let result = `The sum of ${a} and ${b} is ${a+b}.`;
console.log(result); // The sum of 10 and 20 is 30.

While in ES5, to achieve the same we had to write perform like this:

// Multi-line string
var str = 'The quick brown fox\n\t'
+ 'jumps over the lazy dog.';

// Creating string using variables and expression
var a = 10;
var b = 20;
var result = 'The sum of ' + a + ' and ' + b + ' is ' + (a+b) + '.';
console.log(result); // The sum of 10 and 20 is 30.

Default Values for Function Parameters

In ES6, you can now assign default values to function parameters. This allows the function to use these default values if no arguments are passed when it's called. This feature has been eagerly anticipated in JavaScript. Here's an example:

function sayHello(name='World') {
return `Hello ${name}!`;
}

console.log(sayHello()); // Hello World!
console.log(sayHello('John')); // Hello John!

While in ES5, to achieve the same we had to write perform like this:

function sayHello(name) {
var name = name || 'World'; 
return 'Hello ' +  name + '!';
}

console.log(sayHello()); // Hello World!
console.log(sayHello('John')); // Hello John!

Arrow Functions

Arrow Functions are a notable feature in ES6 that offers a more concise syntax for writing function expressions, eliminating the need for the function and return keywords.

They are defined using the fat arrow notation (=>). Let's take a look at how they appear:

// Function Expression
var sum = function(a, b) {
return a + b;
}
console.log(sum(2, 3)); // 5

// Arrow function
var sum = (a, b) => a + b;
console.log(sum(2, 3)); // 5

As observed, arrow functions do not utilize the function and return keywords in their declaration.

For functions with exactly one parameter, you can omit the parentheses (). However, parentheses are necessary when there are zero parameters or more than one.

If the function body contains more than one expression, you must enclose it in braces {}. In such cases, you also need to explicitly use the return statement to return a value.

Arrow functions can be written in several formats. Here are the most commonly used variations:

// Single parameter, single statement
var greet = name => alert("Hi " + name + "!");
greet("Peter"); // Hi Peter!

// Multiple arguments, single statement
var multiply = (x, y) => x * y;
alert(multiply(2, 3)); // 6


// Single parameter, multiple statements
var test = age => {
if(age > 18) {
alert("Adult");
} else {
alert("Teenager");
}
}
test(21); // Adult

// Multiple parameters, multiple statements
var divide = (x, y) => {
if(y != 0) {
return x / y;
}
}
alert(divide(10, 2)); // 5

// No parameter, single statement
var hello = () => alert('Hello World!');
hello(); // Hello World!

An important distinction between regular functions and arrow functions lies in their handling of this. Unlike a standard function, an arrow function does not possess its own this; instead, it inherits this from its enclosing lexical context where it is defined. In JavaScript, this denotes the current execution context within a function.

To illustrate this concept clearly, let's examine the following examples:

function Person(nickname, country) {
this.nickname = nickname;
this.country = country;

this.getInfo = function() {
// Outer function context (Person object)
return function() {
// Inner function context (Global object 'Window')
alert(this.constructor.name); // Window
alert("Hi, I'm " + this.nickname + " from " + this.country);
};
}
}

var p = new Person('Rick', 'Argentina');
var printInfo = p.getInfo();
printInfo(); // Hi, I'm undefined from undefined

Recreating the identical example using ES6 template literals and arrow function:

function Person(nickname, country) {
this.nickname = nickname;
this.country = country;

this.getInfo = function() {
// Outer function context (Person object)
return () => {
// Inner function context (Person object)
alert(this.constructor.name); // Person
alert(`Hi, I'm ${this.nickname} from ${this.country}`);
};
}
}

let p = new Person('Rick', 'Argentina');
let printInfo = p.getInfo();
printInfo(); // Hi, I'm Rick from Argentina

As demonstrated clearly, in the above example, the this keyword now references the context of the enclosing function of the arrow function, which is the Person object (line no-9). This is unlike the previous example where it referred to the global object Window (line no-9).


Classes

In ECMAScript 5 and earlier versions, JavaScript did not have classes. Classes were introduced in ES6, resembling classes in other object-oriented languages like Java and PHP, but with some differences. ES6 classes simplify object creation, support inheritance using the extends keyword, and promote code reuse.

In ES6, you declare a class using the class keyword followed by the class name. Conventionally, class names are written in TitleCase, where the first letter of each word is capitalized.

class Rectangle {
// Class constructor
constructor(length, width) {
this.length = length;
this.width = width;
}

// Class method
getArea() {
return this.length * this.width;
}
}

// Square class inherits from the Rectangle class
class Square extends Rectangle {
// Child class constructor
constructor(length) {
// Call parent's constructor
super(length, length);
}

// Child class method
getPerimeter() {
return 2 * (this.length + this.width);
}
}

let rectangle = new Rectangle(5, 10);
alert(rectangle.getArea()); // 50

let square = new Square(5);
alert(square.getArea()); // 25
alert(square.getPerimeter()); // 20

alert(square instanceof Square); // true
alert(square instanceof Rectangle); // true
alert(rectangle instanceof Square); // false

In the example above, the Square class inherits from Rectangle using the extends keyword. Classes that inherit from other classes are known as derived classes or child classes.

It's crucial to call super() in the constructor of the child class before accessing this. If you omit super() and attempt to call the getArea() method on a square object, an error will occur because the getArea() method requires access to the this keyword.

Note: Unlike function declarations, class declarations are not hoisted. They remain in the temporal dead zone (TDZ) until the execution reaches their declaration, similar to how let and const declarations behave. Therefore, you must define your class before using it, or else a ReferenceError will occur.


Modules

Prior to ES6, JavaScript lacked native support for modules. This meant that everything within a JavaScript application, such as variables across different JavaScript files, shared the same scope.

ES6 introduces file-based modules, where each module is represented by a separate .js file. Now, you can utilize the export and import statements within a module to export or import variables, functions, classes, or any other entities to or from other modules or files.

Let's create a module, for example, a JavaScript file named "main.js", and include the following code in it:

let greet = "Hello World!";
const PI = 3.14; 

function multiplyNumbers(a, b) {
return a * b;
}

// Exporting variables and functions
export { greet, PI, multiplyNumbers };

Now, create another JavaScript file named "app.js" and include the following code:

import { greet, PI, multiplyNumbers } from './main.js';

alert(greet); // Hello World!
alert(PI); // 3.14
alert(multiplyNumbers(6, 15)); // 90

To finish, create an HTML file named "test.html" with the code provided below. Open this HTML file in your browser using HTTP protocol (or localhost). Also, make sure to include type="module" in the script tag.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ES6 Module Demo</title>
</head>
<body>
<script type="module" src="app.js"></script>
</body>
</html>

Understanding Rest Parameters

ES6 introduces a feature called rest parameters, which enables us to pass an arbitrary number of parameters to a function as an array. This is particularly useful when you need to handle a variable number of arguments in a function.

To define a rest parameter, you use the rest operator (...) followed by the parameter name. The rest parameter must always be the last parameter in the function definition, and a function can have only one rest parameter. Below is an example illustrating how rest parameters are used:

function sortNames(...names) {
return names.sort();
}

alert(sortNames("Sarah", "Harry", "Peter")); // Harry,Peter,Sarah
alert(sortNames("Tony", "Ben", "Rick", "Jos")); // John,Jos,Rick,Tony

If a function has a rest parameter as its sole parameter, that rest parameter captures all arguments passed to the function. Otherwise, it captures any additional arguments beyond those explicitly named.

function myFunction(a, b, ...args) {
return args;
}

alert(myFunction(1, 2, 3, 4, 5)); // 3,4,5
alert(myFunction(-7, 5, 0, -2, 4.5, 1, 3)); // 0,-2,4.5,1,3

 

Note: Rest parameters should not be confused with REST (REpresentational State Transfer), which is unrelated to these JavaScript function parameters.


The Spread Operator

The spread operator, represented by (...), functions inversely to the rest operator. It expands an array by breaking it down into individual elements and then passes these elements into a designated function. The example below illustrates its usage:

function addNumbers(a, b, c) {
return a + b + c;
}

let numbers = [5, 12, 8];

// ES5 way of passing array as an argument of a function
alert(addNumbers.apply(null, numbers)); // 25

// ES6 spread operator
alert(addNumbers(...numbers)); // 25

The spread operator also facilitates inserting elements of one array into another array directly, without necessitating array methods such as push(), unshift(), concat(), etc.

let pets = ["Cat", "Dog", "Parrot"];
let bugs = ["Ant", "Bee"];

// Creating an array by inserting elements from other arrays
let animals = [...pets, "Tiger", "Wolf", "Zebra", ...bugs];

alert(animals); // Cat,Dog,Parrot,Tiger,Wolf,Zebra,Ant,Bee

Destructuring Assignment

Destructuring assignment simplifies the extraction of values from arrays or properties from objects into distinct variables, offering a concise syntax.

There are two types of destructuring assignment expressions: array and object destructuring assignments. Let's delve into how each of them functions:

The array destructuring assignment

Before ES6, to retrieve individual values from an array, we used to employ syntax like this:

// ES5 syntax
var fruits = ["Apple", "Banana"];

var a = fruits[0];
var b = fruits[1];
alert(a); // Apple
alert(b); // Banana

In ES6, achieving the same result can be done in a single line using array destructuring assignment:

// ES6 syntax
let fruits = ["Apple", "Banana"];

let [a, b] = fruits; // Array destructuring assignment

alert(a); // Apple
alert(b); // Banana

You can also utilize the rest operator within the array destructuring assignment, demonstrated here:

// ES6 syntax
let fruits = ["Apple", "Banana", "Mango"];

let [a, ...r] = fruits;

alert(a); // Apple
alert(r); // Banana,Mango
alert(Array.isArray(r)); // true

The object destructuring assignment

In ES5, to extract property values from an object, we typically wrote code like this:

// ES5 syntax
var person = {name: "Peter", age: 28};

var name = person.name;
var age = person.age;

alert(name); // Peter
alert(age); // 28

But in ES6, you can easily extract property values from an object and assign them to variables like this:

// ES6 syntax
let person = {name: "Peter", age: 28};

let {name, age} = person; // Object destructuring assignment

alert(name); // Peter
alert(age); // 28

Most of the functionalities we've discussed are supported in the latest versions of major web browsers like Google Chrome, Mozilla Firefox, Microsoft Edge, Safari, and others.

Alternatively, you can utilize online transpilers (source-to-source compilers) such as Babel at no cost. These tools can transpile your current ES6 code into ES5, ensuring better browser compatibility while retaining the enhanced syntax and capabilities of ES6.