Modern JavaScript (ES6) Hands-On Techniques

Modern JavaScript (ES6) Hands-On Techniques

JavaScript/ECMAScript 2015(ES6) is keep updating these days, So I will write about ES6 and main focus is to use React.js framework. I have summarized what I see as the major new features released in each ECMA Script version.

Versions of JavaScript

ES5 (2009)

  • Standard Modules import and export
  • Standardized Promises
  • Classes & Inheritance
  • Block-scoped variables -- let and const
  • Template Literals
  • Object destructing into variables
  • Generator functions
  • Map and Set data structures
  • Internationalization for Strings, Numbers and Dates via Intl API

ES6 / ES2015

  • Array.includes()
  • Numeric exponent (power of) operator **

ES8 / ES2017

  • async functions
  • Object.entries
  • String Padding functions

ES9 / ES2018

  • Object Rest/Spread const obj = { ...props };
  • Asynchronous Iteration for await (...) {
  • Promise finally() function
  • Regular expression enhancements (lookbehind, named groups)

2015 - ECMAScript 2015 (ES6)

Some of the features of ES6:

  • Variable Creation using let and const
  • Template Strings
  • Arrow functions
  • Rest and Spread Operator
  • Destructuring
  • Array functions find() and findIndex()
  • classes
  • promises

es6.png

Babel is a Preprocessor of JavaScript/ECMAScript. it is mainly used to convert ES6+ code into a backwards compatible version of JavaScript that can be run by older browsers.

Variable Creation using let and const

letis like a replacement of var.

const is used to create variables with constant values. The variables created using const cannot be updated later in the code.

const PROJECT_URL = "https://github.com/jaykiran/100_DAYS_OF_CODE";

let num = 100;
let name = "jay";
let arr = [1, 2, 3];
let obj = {
    title: "Readme",
    description: "Modern JavaScript"
};

const num1 = 20;

Hoisting is not possible using let

num = 11;
console.log(num);
var num;

So, In the above case Hoisting is possible. But if we replace the var keyword with let, then the value of num will not be printed in the console.

Template Strings

Template string allows Embedding Expressions inside it. The Expressions are embedded by wrapping them inside ${}.

For example:

let name = "jay";

//normal way
const greetings = "Hello" + " "+ name;

//using template string
const greetings = `Hello ${name}`;
console.log(greetings);

Arrow Functions

ES6 gives us a new syntax of defining functions using a fat arrow. Arrow functions bring a lot of clarity and code reduction.

function getFullName(firstName, lastName){
    return(${firstName} ${lastName});
}
const greetings = (name) => {
    return(`welcome ${name}`);
}

//when there is only one statement, we can write it as
const greetings = (name) => `welcome ${name}`;

Rest and Spread Operators

Rest Operator -

const sum = (num1, num2) => console.log(num1+num2);
sum(1,2,3,4,5);
// Output : 3

The arguments we are calling is greater than the arguments which are defined in the function.

By using Rest Operator, We can call any number of arguments.

const sum = (...args) => console.log(args);
sum(1,2,3,4,5);
//Output : [1,2,3,4,5]

The arguments we passed through function calling is passed as an array to the function. So, it gives the output as array elements.

const sum = (...args) =>{
    let total=0
    for(let i=0; i<args.length; i++){
        total += args[i];
    }
    console.log(total);
}
sum(1,2,3,4,5);
// Output : 15

if we have something like this

const sum = (num1, num2, ...args) =>{
    let total=0
    for(let i=0; i<args.length; i++){
        total += args[i];
    }
    console.log(total);
}
sum(1,2,3,4,5);
// Output : 12

..agrs should be at the end

eg: (num1, num2, ..args)

(..args, num1, num2) will throw an error.

Spread Operator -

Spread Operator allows arrays and objects to be expanded into:

  • Elements in the case of array.
  • Key-Value pairs in the case of Objects.

it can be used for creating the reference elements.

let arr1 = [1,2,3,4,5];
let arr2= [...arr1];

arr1.push(6);
console.log(arr1);
console.log(arr2);

// Output: [1,2,3,4,5,6]
        [1,2,3,4,5]

For Concatenation

let arr1 = [1,2,3,4,5];
let arr2= [6,7,8];

//normal way
let arr3 = arr1.concat(arr2);

//using spread Operator

let arr4 = [...arr1, ...arr2];
console.log(arr3);
console.log(arr4);

//Output: [1,2,3,4,5,6,7,8]
        [1,2,3,4,5,6,7,8]

//sequence of array concatenation also matters here

let arr5 = [0, ...arr1, ...arr2, 9];
console.log(arr5);

//Output: [0,1,2,3,4,5,6,7,8,9]

In case of Objects -

let obj1 = {
    name: "Jay" ,
    lastName: "Guntuku"
}

let obj2 = {
    age: 21
}

let obj3 = {...obj1, ...obj2}
console.log(obj3)

//Output: {name: "Jay", lastName: "Guntuku", age: 21}

Destructuring

It allows us to Unpack arrays or Objects into a bunch of variables which makes working with arrays and objects more convenient.

const name = "Jayakiran Guntuku";
const nameArr = name.split(' ');
console.log(nameArr);
//Output: ["Jayakiran", "Guntuku"]

//using Destructuring concept
let [firstName, lastName] = nameArr;
console.log(firstName);
console.log(lastName);
//Output: Jayakiran
        Guntuku

In the case of Objects, we use this concept as follows -

let firstName= "Jayakiran",
let lastName= "Guntuku",
let age= 22
const person = {
    firstName,
    lastName,
    age
}
console.log(person);

// Output: {firstName: "Jaykiran", lastName: "Guntuku", age: 22}

Array Functions: map()

map() iterates the array for us and we can pass a callback function to perform some operation in each array item. The updated values can be returned by the callback function to create a new array.

Syntax: arr.map((item)=>{//callbackfunction})

const arr = [1,2,3,4,5];
let newArr = arr.map((item)=>{
    //console.log(item);
    return item*2;
});
console.log(arr);
console.log(newArr);

// Output: [1,2,3,4,5]
        [2,4,6,8,10]

In the case of Objects, we use this concept as follows -

const arr = [
    {
        name: "John Doe",
        experience: 2,
        Role: "Software Developer"
    },
    {
        name: "Elon Musk",
        experience: 10,
        Role: "Founder & CEO"
    }
]

let newArr = arr.map((item, pos)=> {
    console.log(data);
    return {
        name: data.name,
        experience: data.experience
    };
});
console.log(arr);
console.log(newArr);

Output: 
    {
        name: "John Doe",
        experience: 2,
        Role: "Software Developer"
    },
    {
        name: "Elon Musk",
        experience: 10,
        Role: "Founder & CEO"
    },

    0:
       name: "John Doe",
       experience: 2
    1:
       name: "Elon Musk",
       experience: 10

Array Functions: reduce()

reduce() also iterates the array for us and we can pass a callback function to perform some operation in each array item. The difference is reduce() passes the result of callback from one iteration to the next one. This callback result is an Accumulator. The Accumulator can be anything (integer, string, object or array) and must be instantiated and passed when calling reduce().

Syntax: arr.reduce((acc, item)=>{//callbackfunction},acc_default_value)

const arr = [1,2,3,4,5];
const result = arr.reduce((acc, item)=>{
    return acc + item;
},0);
console.log(result);
//Output: 15

Array Functions: filter()

It iterates through the array to create a new array. We can decide which elements should be added in the new array based on some conditions.

Syntax: arr.filter(item =>{//Return true/false to add/skip the current item})

const arr = [1,2,3,4,5];
const resultArr = arr.filter(item =>{
    return true; //Output:  [1,2,3,4,5]
    return false; //Output: 
    return item % 2 ==0 //Output: [2,4] 
});
console.log(resultArr);

Array Functions: find() and findIndex()

find() is used to search for element in the array that matches some condition. it returns the first element that matches the condition.

findIndex() returns the index of the element.

const arr = [1,2,3,4,5];
const resultArr = arr.find(item =>{
    return true; //Output: 1
    return false; //Output: undefined
    return item % 2 === 0 //Output: 2 
});
console.log(resultArr);

const resultArr = arr.findIndex(item =>{
    return item % 2 === 0 //Output: 1 
});
console.log(resultArr);

Classes

Classes are a template for creating objects. They encapsulate data with code to work on that data.

class ClassName{
    constructor(){
        //initialize Properties here;
    }
    //Methods Outside constructor
    method1 = () =>{
        //Method Body
    }
}

The body of a class is executed in strict mode, i.e., code written here is subject to stricter syntax for increased performance.

Strict mode makes several changes to normal JavaScript semantics:

  • Eliminates some JavaScript silent errors by changing them to throw errors.
  • Fixes mistakes that make it difficult for JavaScript engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that's not strict mode.
  • Prohibits some syntax likely to be defined in future versions of ECMAScript

Example -

function person(name, birthYear){
    this.name = name;
    this.birthYear = birthYear;

    this.getDetails = function(){
        return "Name: " + this.name + "and age: " + (2020- this.birthYear);
    }
}

var jay = new person('jay', 1998);
console.log(jay.getDetails);

//Output: Name: jay and age: 22
class person{
    constructor(name, birthYear){
        this.name = name;
        this.birthYear = birthYear;
    }

    getDetails = function(){
        return `Name: ${this.name} and age: ${2019-this.birthYear} `
    }
}

var jay = new person('jay', 1998);
console.log(jay.getDetails);

//Output: Name: jay and age: 22

Inheritance

Inheritance is to create "child" object classes (constructors) that inherit features from their "parent" classes.

class ChildClass{
    //body
}
class ChildClass extends ParentClass{
    //body
}

Example -

class person{
    constructor(name, birthYear){
        this.name = name;
        this.birthYear = birthYear;
    }

    getDetails = function(){
        return `Name: ${this.name} and age: ${2019-this.birthYear} `
    }
}

class Pilot extends Person{
    constructor(name, birthYear, exp, type, license){
        super(name, birthYear);
        this.experience = exp;
        this.type = type;
        this.license = license;
    }

    getData = function(){
        return `${this.getDetails} and Experience: ${this.experience} and type: ${this.type} `
    }
}

var jay = new Pilot("Uday", 1999, 28, 'Private', 'XYZ123');
console.log(jay);
jay.getData;

Callbacks & Promises

A promise is used to handle the asynchronous result of an operation. JavaScript is designed to not wait for an asynchronous block of code to completely execute before other synchronous parts of the code can run. For instance, when making API requests to servers, we have no idea if these servers are offline or online, or how long it takes to process the server request.

promise.png

With Promises, we can defer execution of a code block until an async request is completed. This way, other operations can keep running without interruption.

Promises have Three States:

  • Pending: This is the initial state of the Promise before an operation begins
  • Fulfilled: This means the specified operation was completed
  • Rejected: The operation did not complete; an error value is usually thrown

Creating a Promise

The Promise object is created using the new keyword and contains the promise; this is an executor function which has a resolve and a reject callback. As the names imply, each of these callbacks returns a value with the reject callback returning an error object.

const myPromise = new Promise((resolve, reject) => {
    //promise body
    //call resolve() when the operation is complete
    //call reject() when the operation is failed.
})

Using a Promise

Using a promise that has been created is straightforward; we use .then() and .catch() to our Promise like:

myPromise 
  .then(function(done) {
    // the content from the resolve() is here
  })
  .catch(function(error) {
    // the info from the reject() is here
  });

Example -

// creating a promise
const weather = true
const date    = new Promise(function(resolve, reject) {
  if (weather) {
    const dateDetails = {
      name:     'Cubana Restaurant',
      location: '55th Street',
      table:    5
    };

    resolve(dateDetails)
  } else {
    reject(new Error('Bad weather, so no Date'))
  }
});

//using promise
date
  .then(function(done) {
    // the content from the resolve() is here
  })
  .catch(function(error) {
    // the info from the reject() is here
  });

Async and Await

An async function is a modification to the syntax used in writing promises. An async function returns a promise -- if the function returns a value, the promise will be resolved with the value, but if the async function throws an error, the promise is rejected with that value.

function foo() {
  return Promise.reject(25)
}

// is equal to
async function() {
  throw 25;
}

Await is only used with an async function. The await keyword is used in anasync function to ensure that all promises returned in the async function are synchronized, ie. they wait for each other. Await eliminates the use of callbacks in .then() and .catch(). In using async and await, async is prepended when returning a promise, await is prepended when calling a promise. try and catch are also used to get the rejection value of an async function.

async function f() {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => resolve("done!"), 1000)
  });

  let result = await promise; // wait until the promise resolves
  alert(result); // "done!"
}

f();

You can learn more about promises here.

Conclusion

Understanding the concepts of Callbacks, Promises, and async/await can be tricky sometimes, but till no we have seen how they will work when carrying out asynchronous operations and other ES6 features in JavaScript.

They techniques will come in handy a lot when making API requests and event handling.

Did you find this article valuable?

Support Jayakiran Guntuku by becoming a sponsor. Any amount is appreciated!