Introduction to ES6

Variables

ES6 Variable types

  • ES6 introduced three new variable types: let, const, and var.

let variables are block-scoped, meaning that they can only be accessed within the block in which they are declared.


{
  let name = "Janardhan";
  console.log(name); // "Janardhan"
}

console.log(name); // ReferenceError: name is not defined

Const

  • const variables are also block-scoped, but they are also immutable, meaning that their values cannot be changed.
const age = 25;

// Trying to reassign the value of age will result in a TypeError.
age = 26; // TypeError: Assignment to constant variable.

Var

  • var variables are function-scoped, meaning that they can be accessed anywhere within the function in which they are declared, even within nested blocks.
function myFunction() {
  var name = "Janardhan";

  {
    console.log(name); // "Janardhan"
  }

  console.log(name); // "Janardhan"
}

myFunction();

Benefits of using let and const

  • let and const variables offer several benefits over var variables:

    • They are more block-scoped, which can help to prevent variable collisions.
    • They are more concise and readable.
    • They can help to prevent errors, such as typos and reassignment of immutable variables.

When to use let, const, and var

  • Use let to declare a variable that you need to reassign within a block.
  • Use const to declare a variable that you do not need to reassign within a block.
  • Use var to declare a variable that you need to access globally.

Literals

Template literals.

  • Template literals is a new feature in ES6 to make working with strings and string templates easier.
  • Multiline strings without hacks.
  • String formatting - the ability to substitute parts of the string for values contained in variables.
  • Tagged templates - string tagging for safe HTML escaping, localisation and more.

Basic syntax:

let message = `Hello world!`;

console.log(message); // "Hello world!" 
console.log(typeof message); // "string"

Multiline strings (1/3).

In ES5, when using double or single quotes, strings must be completely contained on a single line. There are workarounds for this shortcoming.

Workarounds with join and \n.

var message = "Multiline \
string";

console.log(message); // "Multiline string"

var message1 = [
"Multiline ",
"string"
].join("\n");

let message3 = "Multiline \n" +
"string";

Multiline strings (2/3).

In ES6, template literals make multiline strings easy.

let message = `Multiline
string`;

console.log(message); // "Multiline
// string"

All whitespaces inside the backticks are parts of the string, so be careful with indentation.

let message = `Multiline
string`;

console.log(message); // "Multiline
// string"

Multiline strings (3/3).

Another Example:

let html = `
<div>
  <h1>Title</h1>
</div>`;

String substitution (1/3).

  • Substitutions allow any valid JavaScript expression inside a template literal and output the result as part of the string.
  • Substitutions are delimited by an opening ${ and a closing } that can have any JavaScript expression inside.
var term = "Keeravani";
const url = `https://itunes.apple.com/search?term=${term}&media=music&limit=20`;
console.log(url);
// => https://itunes.apple.com/search?term=keeravani&media=music&limit=20

var a = 10;
var b = 10;
console.log(`The number of JS MVC frameworks is
${2 * (a + b)} and not ${10 * (a + b)}.`);
// => The number of JS frameworks is 40 and not 200.

String substitution (2/3).

The ${} works fine with any kind of expression, including member expressions, function and method calls.

function fn() {
return "I am a result. Rarr";
}

console.log(`foo ${fn()} bar`);

// => foo I am a result. Rarr bar.
var user = {
name: 'Laxman'
};

console.log(`Thanks for helping us, ${user.name.toUpperCase()}.`);

// => "Thanks for helping us, LAXMAN";

String substitution (3/3).

Advanced example of building HTML structures.

var li = (obj) => `<li><a href="${obj.url}">${obj.label}</a></li>`;
var ul = (arr) => `<ul>${arr.map((obj) => li(obj)).join('\n')}</ul>`;
var arr = [
{url: "http://www.twitter.com", label: "Twitter"},
{url: "http://www.linkedin.com", label: "Linked In"},
{url: "http://www.facebook.com", label: "Facebook"}
];

document.getElementById('list').innerHTML = ul(arr);

This will generate the following output:

<ul>
  <li><a href="http://www.twitter.com">Twitter</a></li>
  <li><a href="http://www.linkedin.com">Linked In</a></li>
  <li><a href="http://www.facebook.com">Facebook</a></li>
</ul>

Destructuring

What is destructuring?

  • Destructuring is a convenient way to extract values from data stored in Objects and Arrays (also nested). This is an opposite operation to structuring (constructing data).*
var obj = {first: 'Jane', last: 'Leo'};

Destructuring in ECMAScript 6 enables the same syntax for extracting data. Just as the object literal lets us create multiple properties at the same time, the object pattern lets us extract multiple properties at the same time.

var {first: a, last: b} = obj;
// a = 'Jane', b = 'Leo'

var [x, y] = ['Jane', 'Leo'];
// x = 'Jane', y = 'Leo'

Object destructuring.

var foo = {bar: 'pony', baz: 3};
var {bar: a, baz: b} = foo;

console.log(a)
// 'pony'
console.log(b)
// 3

ES5 equivalent

var foo = {bar: 'pony', baz: 3};
var a = foo.bar;
var b = foo.baz;

Object destructuring, shorthand notation.

Only for object literals: If the value of a property is provided via a variable whose name is the same as the property name, you can omit the property name.

var {x, y} = {x: 11, y: 8};
    
console.log(x)
// 11
console.log(y)
// 8  

This declaration is equivalent to:

var {x: x, y: y} = {x: 11, y: 8};

Object destructuring, nested properties.

You can also pull properties as deep as you want.

var foo = {bar: {deep: 'pony', smile: 'lol'}};
var {bar: {deep, smile: sure}} = foo;
    
console.log(deep);
// 'pony'
console.log(sure);
// 'lol'

ES5 equivalent

var foo = {bar: {deep: 'pony', smile: 'lol'}};
var deep = foo.bar.deep;
var sure = foo.bar.smile;

Array destructuring.

Destructuring works for arrays as well. How? By using square brackets in the destructuring side of the declaration.

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

console.log("A is " + a + ", B is " + b + ", C is " + c);
// A is 1, B is 2, C is 3

The rest operator lets you extract the remaining elements of an array into an array. You can only use this operator as the last part inside an array.

var [x, ...y] = ['a', 'b', 'c'];
// x = 'a', y = ['b', 'c']

Attention: The same syntax (…) is used by the spread operator!

Pick what you need.

If you destructure an object, you mention only those properties that you are interested in.

var {x} = {x: 7, y: 3, z: 10};
// x = 7

If you destructure an array, you can choose to only extract a prefix.

var [x, y] = ['a', 'b', 'c'];
// x = 'a', y = 'b';

You can also conveniently skip over elements that you don’t care about.

var [, , a, b] = [1, 2, 3, 4, 5];
// a = 3, b = 4;
    
function f() {return [1, 2, 3];}

var [a, , b] = f();
// a = 1, b = 3;

Mix objects and arrays.

We can also mix objects and arrays together and use theirs literals.

var mixed = {
  one: 1, two: 2, values: [3, 4, 5]
};

var {one: a, two: b, values: [c, , e]} = mixed;

console.log(a, b, c, e);
// 1 2 3 5

The same with return value of a function:

function mixed () {
  return {
    one: 1, two: 2, values: [3, 4, 5]
  };
}

var {one: a, two: b, values: [c, , e]} = mixed();

If a part has no match (1/3).

If the value of a destructuring assignment isn’t match, it evaluates to undefined. Just like when accessing not defined properties on an object with the dot or bracket notation.

var [x, y] = [];
    
console.log(x);  // undefined
console.log(y);  // undefined    
    
var point = {
  x: 1
};

var {x: a, y: b} = point;
    
console.log(a);  // 1
console.log(b);  // undefined

If a part has no match (2/3).

Example of deep objects:

var {
  prop1: x,
  prop2: {
    prop3: {
      nested: [ , , y]
    }
  }
} = {prop: "Hello", prop2: {prop3: {nested: ["a", "b", "c"]}}};

Question: What are the values of variables x and y?

console.log(typeof x);  // undefined
console.log(y);         // 'c'

If a part has no match (3/3).

If you’re trying to access a deeply nested property of a parent that doesn’t exist, then you’ll get an exception.

var {foo: {bar}} = {baz: 'something'};
    
// Exception: TypeError

It makes sense if you rewrite destructuring in ES5:

var temp = {baz: 'something'};
var bar = temp.foo.bar;
    
// Exception: TypeError

Default values (1/3).

If a part (an object property or an array element) has no match in the source, it is matched against:

  • its default value (if specified)
  • undefined (otherwise)

Providing a default value is optional. Example with array:

var [x = 3, y] = [];
// x = 3; y = undefined

Explanation: The element at index 0 has no match on the right-hand side. Therefore, destructuring continues y matching x against 3, which leads to x being set to 3.

Example with object:

var {foo: x = 3, bar: y} = {};
// x = 3; y = undefined

Default values (2/3).

undefined triggers default values. Default values are also used if a part does have a match and that match is undefined.

var {prop: y = 2} = {prop: undefined};
// y = 2

Default values are computed when they are needed (on demand). This

var {prop: y = someFunc()} = someValue;

is equivalent to

var y;
if (someValue.prop === undefined) {
    y = someFunc();
} else {
    y = someValue.prop;
}

Default values (3/3).

So far we have only seen default values for variables, but you can also associate them with object itself:

var [{prop: x} = {}] = [];

What does this mean? The element at index 0 has no match, which is why destructuring continues with:

var {prop: x} = {};
// x = undefined

Another example with complex default values:

var [{prop: x} = {prop: 123}] = [];
// Destructuring continues as follows:
var {prop: x} = {prop: 123};
// x = 123

Destructuring in a function’s parameter list.

You can also use destructuring in a function’s parameter list.

function greet({name: greeting = 'she', age}) {
  console.log(`${greeting} is ${age} years old.`)
}
    
greet({name: 'nico', age: 27});
// nico is 27 years old
    
greet({age: 24});
// she is 24 years old

Next advanced example:

var ajax = function({url = 'sbb.ch', port: p = 80}, ...data) {
  console.log("Url:", url, "Port:", p, "Rest:", data);
};
    
ajax({url: "localhost"}, "more", "data", "hello");
// => Url: localhost Port: 80 Rest: ['more', 'data', 'hello']

Destructuring with computed property names.

Computed property names are another object literal feature that also works for destructuring. You can specify the name of a property via an expression, if you put it in square brackets.

var KEY = 'z';
var {[KEY]: foo} = {z: 'bar'};

console.log(foo);
// 'bar'

Good to know.

If we try to omit var, let or const, it will throw an error, because block code can’t be a destructuring assignment.

let point = {
  x: 1
};

{x: a} = point; // throws error

Solution: We have to wrap it in parentheses.

let point = {
  x: 1
};

({x: a} = point);

console.log(a);
// 1

Arrow Functions

Arrow functions. Syntax (1/4)

  • Arrow functions are anonymous functions that use an “arrow” =>.
  • The syntax for arrow functions comes in many variations.
  • All variations begin with function arguments, followed by the arrow, followed by the body of the function.
  • Single argument can be used directly without any parentheses.
  • The expression to the right of the arrow is evaluated and returned.

Arrow functions. Syntax (1/4)

// ES6
[1, 2, 3].map(num => num * 2);

// ES5 equivalent
[1, 2, 3].map(function(num) {return num * 2;});

No return keyword is required with a single statement.

Arrow functions. Syntax (2/4).

  • Must include parentheses around the arguments if there are more than one argument.
//ES6
var sum = (num1, num2) => num1 + num2;

//ES5 equivalent
var sum = function(num1, num2) {
return num1 + num2;
};
  • No arguments (the same syntax as for many arguments).
var doNothing = () => {};

Arrow functions. Syntax (3/4).

  • if function has more statements and not just an expression to return, you have to use brackets.
//ES6
[1, 2, 3, 4].map(num => {
var multiplier = 2 + num;
return num * multiplier;
});

//ES5 equivalent
[1, 2, 3, 4].map(function(num) {
var multiplier = 2 + num;
return num * multiplier;
});

Arrow functions. Syntax (4/4).

There is a special case - an arrow function that wants to return an object literal {...}. In this case, the function body must wrap the literal in parentheses.

//ES6
var getSomeObject = id => ({key: id, value: "unknown"});

//ES5 equivalent
var getSomeObject = function(id) {
return {
   key: id,
   value: "unknown"
  };
};

Arrow functions. IIFEs.

Immediately-invoked function expressions (IIFEs) allow you to define an anonymous function and call it immediately without saving a reference.

let person = function(message) {
return {
      getGreeting: function() {
      return message;
    }
 };
}("Hell World!");

//Using arrow Functions

let person = ((message) => {
return {
   getGreeting: function() {
    return message;
  }
};
})("Hell World!");

Classes

Class-like structures in ES 5.

function Car () {
  this.fuel = 0;
  this.distance = 0;
}

Car.prototype.move = function () {
  if (this.fuel < 1) {
    throw new RangeError('Fuel tank is depleted')
  }
  this.fuel--
  this.distance += 2
}

Car.prototype.addFuel = function () {
  if (this.fuel >= 60) {
    throw new RangeError('Fuel tank is full')
  }
  this.fuel++
}    

var car = new Car();
car.addFuel();
car.move();

Class declaration in ES6 (1/2).

  • ES6 classes are syntactic sugar over the prototype-based OO pattern.
  • Classes support prototype-based inheritance, super calls, instance and static methods and constructors.
class Car {
  constructor () {
    this.fuel = 0
    this.distance = 0
  }
    
  move () {
    if (this.fuel < 1) {
      throw new RangeError('Fuel tank is depleted')
    }
    this.fuel--
    this.distance += 2
  }
    
  addFuel () {
    if (this.fuel >= 60) {
      throw new RangeError('Fuel tank is full')
    }
    this.fuel++
  }
}    

Class declaration in ES6 (2/2).

Important notes:

  • Class declarations begin with the class keyword followed by the name of the class.
  • Commas are invalid between methods in a class.
  • Properties that occur on the instance rather than the prototype, can only be created inside a class constructor or method. In the example these are fuel and distance.
  • All methods are non-enumerable. They don’t show up when you do a for...in loop over the object or use Object.keys() to get an array of property names. But you can iterate over class methods by Object.getOwnPropertyNames(MyClass.prototype).
  • Calling the class constructor without new throws an error.
  • Attempting to overwrite the class name within a class method throws an error.

Class expressions (1/3)

Classes and functions are similar in that they have two forms: declarations and expressions. - Function and class declarations begin with an appropriate keyword (function or class, respectively) followed by an identifier. - Functions have an expression form that doesn’t require an identifier after function, and similarly, classes have an expression form that doesn’t require an identifier after class.

let PersonClass = class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
};

let person = new PersonClass("Nicholas");
person.sayName();   // outputs "Nicholas"    

Class expressions (2/3)

The previous example used an anonymous class expression. The name property of PersonClass (PersonClass.name) is an empty string. When using a class declaration, PersonClass.name would be "PersonClass".

A named class expression includes an identifier after the class keyword.

let PersonClass = class PersonClass2 {
    constructor(name) {
      this.name = name;
    }
  
    sayName() {
      console.log(this.name);
    }
};

console.log(typeof PersonClass);   // "function"
console.log(typeof PersonClass2);  // "undefined"  

The PersonClass2 identifier exists only within the class definition. It is defined for use only inside the class. Outside the class, typeof PersonClass2 is undefined because no PersonClass2 binding exists there.

Class expressions (3/3)

Another interesting use of class expressions is creating singletons by immediately invoking the class constructor. To do so, you must use new with a class expression and include parentheses at the end.

let person = new class {
  constructor(name) {
    this.name = name;
  }

  sayName() {
    console.log(this.name);
  }
}("Nicholas");

person.sayName();   // "Nicholas"

Inheritance (1/4).

Classes make inheritance easier to implement by using the familiar extends keyword.

class Warrior {
  constructor(name) {
    this.name = name;
    this.health = 100;
  }
    
  heal(amount) {
    this.health = Math.min(this.health + amount, 100);
  }
}

class Ninja extends Warrior {
  constructor(name) {
    super(name);
  }

  heal(amount) {
    super.heal(amount * 1.2);
  }
}

Inheritance (2/4).

Derived classes require you to use super() if you specify a constructor; if you don’t, an error will occur. If you choose not to use a constructor, then super() is automatically called for you with all arguments upon creating a new instance of the class. For instance, the following two classes are identical:

class Square extends Rectangle {
  // no constructor
}

// Is equivalent to

class Square extends Rectangle {
  constructor(...args) {
    super(...args);
  }
}

Important notes:

  • You can only use super() in a derived class. If you try to use it in a non-derived class (a class that doesn’t use extends), it will throw an error.
  • You must call super() before accessing this in the constructor. Attempting to access this before calling super() results in an error.

Inheritance (3/4).

The methods on derived classes always shadow methods of the same name on the base class.

class Square extends Rectangle {
  constructor(length) {
    super(length, length);
  }

  // override and shadow Rectangle.prototype.getArea()
  getArea() {
    return this.length * this.length;
  }
}

You can also call the base class version of the method by using the super. E.g.

class Square extends Rectangle {
  ...    
  // override, shadow, and call Rectangle.prototype.getArea()
  getArea() {
    return super.getArea();
  }
}

Inheritance (4/4).

In ES5 and earlier, it was not possible to inherit from built-ins like Array, RegExp, etc. It is possible in ES6.

class MyArray extends Array {
  // empty
}

var colors = new MyArray();
colors[0] = "red";
console.log(colors.length);   // 1

Any method that returns an instance of the built-in will automatically return a derived class instance instead. So, if you have a derived class called MyArray that inherits from Array, methods such as slice() return an instance of MyArray.

let items = new MyArray(1, 2, 3, 4),
    subitems = items.slice(1, 3);

console.log(items instanceof MyArray);      // true
console.log(subitems instanceof MyArray);   // true

Accessors (1/2).

We have the ability to define getters and setters for our fields. This feature allows to access the fields as any other object property.

class Warrior {
  constructor(name) {
    this._name = name;
    this._health = 100;
  }

  get health() {
    return this._health;
  }
    
  set health(newHealth){
    this._health = newHealth;
  }
}

let donatello = new Warrior("Donatello");
    
console.log(donatello.health);   // 100
donatello.health = 80;

Accessors (2/2).

When we define a getter and no setter, the variable with the getter / setter name becomes read-only.

class Warrior {
  constructor(name) {
    this._name = name;
    this._health = 100;
  }

  get health() {
    return this._health;
  }
}

let donatello = new Warrior("Donatello");
    
console.log(donatello.health);   // 100
donatello.health = 80;   // TypeError: Cannot set property health...

Static methods (1/3).

Adding additional methods directly onto constructors to simulate static members is a common pattern in ES5 and earlier.

function PersonType(name) {
  this.name = name;
}

// static method
PersonType.create = function(name) {
  return new PersonType(name);
};

// instance method
PersonType.prototype.sayName = function() {
  console.log(this.name);
};

var person = PersonType.create("Nicholas"); 

Static methods (2/3).

ES6 classes simplify the creation of static members by using the static annotation before the method or accessor property name.

class PersonClass {
  constructor(name) {
    this.name = name;
  }
    
  sayName() {
    console.log(this.name);
  }
    
  static create(name) {
    return new PersonClass(name);
  }
}

let person = PersonClass.create("Nicholas");

Static methods (3/3).

One thing you can do in JavaScript but can’t in Java: from the derived class you can override and call your parents static method.

class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
  static classMethod() {
    return super.classMethod() + ', too';
  }
}
    
Bar.classMethod();   // 'hello, too'

Static fields.

There are no static fields in ES6, but there is a discussion about it for ES7. Right now you can simulate the functionality like this:

class Foo {
  constructor() {}
}

const staticNumber = 5;
const staticObj = {prop: 5};

Foo.staticNumber = staticNumber;
Foo.staticObj = staticObj;  

Abstract classes.

This concept is not implemented in ES6 or ES7, but we can simulate abstract classes. We can use new.target in class constructors to determine the constructor the class is being instantiated with.

// abstract base class
class Shape {
  constructor() {
    if (new.target === Shape) {
      throw new Error("This class cannot be instantiated directly.")
    }
  }
}

class Rectangle extends Shape {
  constructor(length, width) {
    super();
    this.length = length;
    this.width = width;
  }
}

var x = new Shape();   // throws error because new.target is Shape
var y = new Rectangle(3, 4);   // no error, new.target is Rectangle

Abstract methods.

This concept is not implemented too, but we can mimic abstract methods.

class Abstract {
  constructor() {
    if (this.method === undefined) {
      throw new TypeError("Must override method");
    }
  }
}

class Derived1 extends Abstract {}

class Derived2 extends Abstract {
  method() {}
}

const a = new Abstract(); // this.method is undefined; error
const b = new Derived1(); // this.method is undefined; error
const c = new Derived2(); // this.method is
                          // Derived2.prototype.method; no error    

Private fields.

Private fields are not supported in any ES right now, but there are a couple of new structures in ES6 that let you emulate the private scope.

const private = new WeakMap();

class Warrior {    
  constructor(name) {
    this._name = name;
    
    private.set(this, {
        health: 100
    });    
  }

  get health(){
    return private.get(this).health;
  }
}  

Warning: The above WeakMap is shared among all Warrior instances, so potentially you can access another warrior’s health.

Functions overloading.

This functionality have never existed in JavaScript and there is no plan on supporting it. If you have e.g. three methods of the same name say

class Person {    
  say(what) { //string
    console.log(thing);
  }

  say(things) { //array
    things.forEach(function(thing){
        console.log(thing);
    });        
  }

  say() {
    console.log("blah")
  }
}    

the last say() method wins (overwrites all other).

JavaScript has no type checking on arguments or required quantity of arguments, so you can just have one implementation of say().

Interfaces.

There is no concept of interfaces in any version of JavaScript.

You can find interfaces in TypeScript.

Modules

What are modules?

  • ES6 introduced built-in modules.
  • Defined in its own file.
  • functions or variables defined in a module are not visible outside explicitly export them.
  • To export certain variables from a module use the keyword export.
  • Similarly, to consume the exported variables in a different module you use import.
  • Modules are singletons. imported multiple times, only a single “instance” of it exists.

Exporting declarations.

export in front of any variable, function or class export it from the module.

// export data
export let color = "red";

// export function
export function sum(num1, num2) {
  return num1 + num1;
}

// export class
export class Rectangle {
  constructor(length, width) {
    ...
  }
}

Exporting statements and renaming.

Declare a variable, function or class and export it later.

// define a function and then export it later
function multiply(num1, num2) {
  return num1 * num2;
}

export { multiply };    

Can use the as keyword to specify a new name for importing.

class ZipCodeValidator extends StringValidator {
    isAcceptable(value) {
        return value.length === 5 && numberRegexp.test(value);
    }
}

export { ZipCodeValidator as mainValidator }; 

Re-exporting.

There may be a time when you’d like to re-export something that your module has imported. Use cases: - You’re creating a library out of several small modules. - Modules extend other modules, and partially expose some of their features.

// re-export sum from the module "./example.js"
export { sum } from "./example.js";
    
// sum is imported from "./example.js" and then exported as add
export { sum as add } from "./example.js";
    
// re-export everything from "./example.js"
export * from "./example.js";

Note: If you’d like to export everything from another module, you can use the * pattern.

Importing single, multiple bindings or all.

Once you have a module with exports, you can access the functionality in another module by using the import keyword.

// import just one or multiple bindings
import { sum } from "./example.js";
import { multiply, magicNumber } from "./example.js";

console.log(sum(1, magicNumber));
console.log(multiply(1, 2));

Import the entire module as a single object.

// import everything
import * as example from "./example.js";

console.log(example.sum(1, example.magicNumber));
console.log(example.multiply(1, 2));

Renaming imports.

If the module importing a variable, function or class wants to use a different name, it can use as.

Example:

// import the add() function and renames it to sum()
import { add as sum } from "./example.js";
    
console.log(typeof add);   // "undefined"
console.log(sum(1, 2));    // 3

Note: There is no identifier named add in this module.

Exporting and importing default values.

  • The default value for a module is a single variable, function or class as specified by the default keyword,
  • only set one default export per module. Examples:
export default function(num1, num2) {
    return num1 + num2;
}
function sum(num1, num2) {return num1 + num2;}

export default sum;
  • For importing no curly braces are used.
  • It is also possible to import non default and default things in one import statement.

Examples

import sum from "./example.js";
import sum, { add } from "./example.js";

Resolving paths for importing.

A module can import things from other modules.

  • Relative paths ('../model/user') relatively to the location of the importing module. The file extension can usually be omitted.
  • Absolute paths ('/lib/js/helpers') which point directly to the file of the module to be imported.
  • Names ('util'). What modules names refer to has to be configured. A typically configuration in JavaScript projects refers to the node_modules directory as root.

All supported syntax at a glance.

// import a module without any import bindings
import 'jquery';
// import the default export of a module
import $ from 'jquery';
// import a named export of a module
import { $ } from 'jquery';
// import a named export to a different name
import { $ as jQuery } from 'jquery';
// import an entire module instance object
import * as crypto from 'crypto';
    
// export a named function
export function foo() {};
// export the default export as a function
export default function foo() {};
// export an existing variable
export { encrypt };
// export a variable as a new name
export { decrypt as dec };
// export an export from another module
export { encrypt as en } from 'crypto';
// export all exports from another module
export * from 'crypto';

Loading modules in browsers.

To support modules, the module value was added as a type option of the script element. Setting type to module tells the browser to load any inline code or code contained in the file specified by src as a module.

<!-- load a module JavaScript file -->
<script type="module" src="module.js"></script>
    
<!-- include a module inline -->
<script type="module">
import { sum } from "./example.js";

let result = sum(1, 2);
</script>

Promises

Promises

  • The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

  • A Promise is in one of these states:

    • pending: initial state, neither fulfilled nor rejected.
    • fulfilled: meaning that the operation was completed successfully.
    • rejected: meaning that the operation failed.

Promise methods

  • Promise has two handler methods
    • then() will be called when Promise is fulfilled (success)
    • catch() will be called when something went wrong

fetch(`https://itunes.apple.com/search?term=${term}&media=music&limit=20`)
  .then(res => {
    return res.json()
      .then(data => {
        console.log(data)})
      .catch(err => { alert(err) })})// This will handle if any error happens while converting res->JSON
  .catch(err => { alert(err) }) //This will handle any fetch invocation errors
  

await & async

  • await & async simplifies promise handlers handling
async function serachiTunes(term) {
  try {
        const resp = await fetch(`https://itunes.apple.com/search?term=${term}&media=music&limit=20`);
        const searchResults = await resp.json();
        return searchResults;
    } catch(e) {
      console.error(e)
    }
}
  • enclosed function of await must be marked with async

Others

Default parameters (1/3).

In ECMAScript 5 and earlier, there is the following pattern to create a function with default parameters values:

function makeRequest(url, timeout, callback) {
timeout = timeout || 2000;
callback = callback || function() {};
...
}
  • both timeout and callback are optional parameters that get default values if they are not provided.
  • The OR operator || always returns the second operand when the first is falsy.

Default parameters (2/3).

function makeRequest(url, timeout, callback) {
timeout = (typeof timeout !== "undefined") ?
timeout : 2000;
callback = (typeof callback !== "undefined") ?
callback : function() {};

...
}

This approach still requires a lot of extra code for a very basic operation. ECMAScript 6 makes it easier!

function makeRequest (
url, timeout = 2000, callback = function() {}) {

...
}

This function only expects the first parameter to always be passed. The other parameters have default values.

Default parameters (3/3).

Examples how the function can be called:

// uses default timeout and callback
makeRequest("/foo");

// uses default callback
makeRequest("/foo", 500);

// doesn't use defaults
makeRequest("/foo", 500, function(body) {...});

In the case of default parameter values, a value of null is considered to be valid.

// uses default timeout (undefined = missing parameter)
makeRequest("/foo", undefined, function(body) {...});

// doesn't use default timeout
makeRequest("/foo", null, function(body) {...});

Default parameters and arguments.

arguments object remains detached from the named parameters.

function doSomething(first, second = "b") {
console.log(arguments.length);
console.log(first === arguments[0]);
console.log(second === arguments[1]);
first = "c";
second = "d";
console.log(first === arguments[0]);
console.log(second === arguments[1]);
}

doSomething("a");

The outputs:

1 // length is 1 because only one argument was passed
true // first is equal to arguments[0]
false // because arguments[1] is undefined
false // changing first has no effect on arguments
false // changing second has no effect on arguments

Default parameters expressions.

The default value need not be a simple value. It can be any valid expression, even a function to retrieve the default parameter value.

function getValue() {
return 5;
}

function add(first, second = getValue()) {
return first + second;
}

console.log(add(1, 1)); // 2
console.log(add(1)); // 6

Important: The default value expressions are lazily evaluated, meaning they’re only run when they’re needed (when a parameter’s argument is omitted or is undefined).

Default parameters with cross-usage.

You can use a previous parameter as the default for a later parameter.

function add(first, second = first) {
return first + second;
}

console.log(add(1, 1)); // 2
console.log(add(1)); // 2
function getValue(value) {
return value + 5;
}

function add(first, second = getValue(first)) {
return first + second;
}

console.log(add(1, 1)); // 2
console.log(add(1)); // 7

Default parameters and scopes.

The formal parameters in a function declaration are in their own scope. A reference to an identifier in a default value expression first matches the formal parameters’ scope before looking to an outer scope. Example:

var w = 1, z = 2;

function foo(x = w + 1, y = x + 1, z = z + 1) {
console.log(x, y, z);
}

foo(); // ReferenceError

The w in the w + 1 default value expression looks for w in the formal parameters’ scope, but does not find it, so the outer scope’s w is used. Next, the x in the x + 1 default value expression finds x in the formal parameters’ scope, and luckily x has already been initialized, so the assignment to y works fine.

However, the z in z + 1 finds z as a not-yet-initialized-at-that-moment parameter variable, so it never tries to find the z from the outer scope and throws a ReferenceError.

Spread operator (1/2).

ES6 introduces a new ... operator that’s typically referred to as the spread or rest operator, depending on where/how it’s used. When ... is used in front of an iterable (Array, Set, Map, …), it acts to “spread” it out into its individual values.

A typical usage: Specify an array, split it into items which are passed in as separate arguments to a function.

function foo(x, y, z) {
console.log(x, y, z);
}

foo(...[1,2,3]); // 1 2 3

ES5 way:

foo.apply(null, [1,2,3]); // 1 2 3

That means, the spread operator acts as a simple syntactic replacement for the apply() method.

Spread operator (2/2).

The spread operator can be used to spread out a value in other contexts as well, such as inside another array declaration:

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

console.log(b); // [1, 2, 3, 4, 5]

Here, ... is replacing concat(), as it behaves like [1].concat(a, [5]).

Another example for mixing the spread operator with other arguments:

let values = [-25, -50, -75, -100];
console.log(Math.max(...values, 0)); // 0

ES5 equivalent:

let values = [-25, -50, -75, -100];
console.log(Math.max.apply(Math, values.concat([0]))); // 0

Rest parameters (1/3).

The other common usage of ... can be seen as the opposite - instead of spreading a value out, the ... gathers a set of values together into an array.

function foo(x, y, ...z) {
console.log(x, y, z);
}

foo(1, 2, 3, 4, 5); // 1 2 [3,4,5]

The ...z in this snippet is saying: “gather the rest of the arguments (if any) into an array called z”.

The ...z is called rest parameters because you’re collecting the rest of the parameters.

The rest parameters can be used as a better alternative to arguments (arguments is not really an array, but an array-like object).

Rest parameters were designed to replace arguments in ECMAScript.

Rest parameters (2/3).

ES5

function foo() {
// turn arguments into a real array and remove the first element
var args = Array.prototype.slice.call(arguments);

args.shift();

console.log.apply(console, args);
}

ES6 way (rest parameters and spread operator together)

function foo(...args) {
// args is already a real array, remove the first element
args.shift();

console.log(...args);
}

Rest parameters (3/3).

There are two restrictions on rest parameters.

1. There can be only one rest parameter, and the rest parameter must be last.

// Syntax error: Can't have a named parameter after rest parameters
function doSomething(foo, ...bars, last) {
...
}

2. Rest parameters cannot be used in an object literal setter.

let object = {
// Syntax error: Can't use rest param in setter
set name(...value) {
// do something
}
};

Object literal setters are restricted to a single argument and rest parameters have, by definition, an infinite number of arguments.

☀️