ES2015 (ES6) Tips, Tricks, Best Practices, and Code Snippet Examples for Your Day-to-Day Workflow

dilipmishra

Dilip Mishra

Posted on September 21, 2024

ES2015 (ES6) Tips, Tricks, Best Practices, and Code Snippet Examples for Your Day-to-Day Workflow

ES6 (ECMAScript 2015) brought a significant overhaul to JavaScript, introducing numerous new features that can streamline your coding and enhance the overall quality of your projects.

In this post, we’ll go over some ES2015 tips, tricks, best practices, and provide code snippet examples to enhance your day-to-day workflow.

1. Declaring Variables: let and const vs var

In ES5, variables were declared using var, which had function-scoped behavior, leading to issues with hoisting and scope visibility. ES6 introduced let and const with block-scoping, providing better control over variable declaration.

const:

Define a constant variable:

const variableName = "value"
Enter fullscreen mode Exit fullscreen mode

Constant variables can not be changed or reassign or redefine:

const variableName = "other value"  
   //-->SyntaxError: Identifier 'variableName' has already been declared
variableName = "other value" 
   //-->TypeError: Assignment to constant variable.
Enter fullscreen mode Exit fullscreen mode

You can change, add value to a constant array but you cannot reassign or redefine it:

const arrayName = [1,2,3,4]
arrayName.push(5) 
   // Output -->[1,2,3,4,5]
const arrayName = [9,8,7,6] 
   //-->SyntaxError: Identifier 'arrayName' has already been declared
Enter fullscreen mode Exit fullscreen mode

You can change, add value to a constant object but you cannot reassign or redefine it:

const person = {name:"Developer",email:"developer@developer.com",city:"New Delhi"}
person.name ="Developer 2" //change a property 
person.location = "Gurugram" //add a new property
person = {name:"Dilip",email:"dilip@abc.com",city:"Delhi"} //reassign it 
   //-->SyntaxError: Identifier 'arrayName' has already been declared
Enter fullscreen mode Exit fullscreen mode

Constant variables exist in a block scope:

var x = 1
{ //this is a block scope
    const x = 2
}
console.log(x) 
    //Output -->1
Enter fullscreen mode Exit fullscreen mode

let:

Define a let variable:

let variableName = "value"
Enter fullscreen mode Exit fullscreen mode

let variables exist in a block scope:

var x = 1
{ //this is a block scope
    let x = 2
}
console.log(x) //Output -->1
Enter fullscreen mode Exit fullscreen mode

let variables cannot be redefined, but can be reassigned:

let variableName = "other value"  
    //-->SyntaxError
variableName = "other value"
Enter fullscreen mode Exit fullscreen mode

Hoisting - var vs let:

Variable defined by var get hoisted at the top

console.log(sayHello)
    //Output -->undefined
//variable sayHello is hoisted at the top before it was defined by var
//This means that variable is there but with value of undefined
var sayHello = "Hello World" 
console.log(sayHello)
    //Output -->"Hello World"
Enter fullscreen mode Exit fullscreen mode

Variable defined by let doesn't get hoisted at the top

console.log(sayHello)
     //-->ReferenceError: Cannot access 'sayHello' before initialization/defined
let sayHello = "Hello World"
console.log(sayHello)
    //Output -->"Hello World"
Enter fullscreen mode Exit fullscreen mode

let should be used in for loop instead of var because variables defined by var will be leaked outside the for loop and will only reference the ending result of i if there is a setTimeout function:

with var

for (var i = 0; i < 3; i++) {
     console.log(i);
     setTimeout(function(){
          console.log("The number is " + i);
     }, 1000);
};
//after 1 sec
    //-->The number is 2  (x3)   
//setTimeout reference i after when the for loop ends
console.log(i)
    //--> 2
//i is leaked outside the for loop
Enter fullscreen mode Exit fullscreen mode

with let

for (let i = 0; i < 3; i++) {
     setTimeout(function(){
          console.log("The number is " + i);
     }, 1000);
}
//after 1 sec
    //-->The number is 0
    //-->The number is 1
    //-->The number is 2
Enter fullscreen mode Exit fullscreen mode

Best Practice: Use const for variables that won’t change, and let for variables that need to change within a specific block. Avoid var to prevent scope-related issues.


2. Arrow Function

Arrow function is a new way of defining a function for cleaner code and is commonly used in callback function.
Arrow functions allow us to write shorter function syntax.
Define an arrow function with return:

let myFunction = (a, b) => {
  sum = a * b;
  return sum;
}
Enter fullscreen mode Exit fullscreen mode

Define an arrow function without return:

let myFunction = (a, b) => a * b;
Enter fullscreen mode Exit fullscreen mode

If there is no parameter, you can just use parentheses:

let myFunction = () => ("Hello world");  
Enter fullscreen mode Exit fullscreen mode

Same way before ES6

function functionName(param1,param2){
     return param1+param2;
}
Enter fullscreen mode Exit fullscreen mode

Arrow function is commonly used in callback:

let myArr = [1,2,3]
let doubleThenFilter = arr => arr.map((value) => (value * 2) )
                                  .filter((value) => (value % 3 === 0));
doubleThenFilter(myArr)
Enter fullscreen mode Exit fullscreen mode

Same way before ES6

function doubleThenFilter(arr){
    return arr.map(function(value){
        return value *2;
    }).filter(function(value){
        return value % 3 === 0;
    })
};
Enter fullscreen mode Exit fullscreen mode

Best Practice: Use arrow functions for anonymous functions and callbacks to make your code shorter and to avoid issues with this.


3. Template Literals vs String Concatenation

In ES5, string concatenation required the use of +, making it difficult to manage complex or multi-line strings. ES6 introduced template literals, allowing embedded expressions and multi-line strings using backticks.
Template Literals use back-ticks (` `) rather than the quotes ("") to define a string.
Template string is a quick way for you to handle a string.
You can reference variables:

let first = "Dilip";
let last = "Mishra";

console.log(`Hello, ${first} ${last}`);
//Output --> "Hello, Dilip Mishra"
Enter fullscreen mode Exit fullscreen mode

Same way before ES6:

let first = "Dilip";
let last = "Mishra";
var greeting = 'Hello, ' + name + '!';

console.log('Hello, ' + first + ' ' +last);  
//Output --> "Hello, Dilip Mishra"

Enter fullscreen mode Exit fullscreen mode

Template Literals allow both single and double quotes inside a string:

Template Literals allow multiline strings.
You can just break a line, use tab without using \n \t :

let text =
'The quick
brown fox
jumps over
the lazy dog';
  //Output -->  "The quick
brown fox
jumps over
the lazy dog"
Enter fullscreen mode Exit fullscreen mode

Template Literals allow expressions in strings:

let price = 10;
let VAT = 0.25;

let total = 'Total: ${(price * (1 + VAT)).toFixed(2)}';
  //Output -->  "Total: 12.50"
Enter fullscreen mode Exit fullscreen mode

Best Practice: Use template literals for better readability when working with strings that involve dynamic content or span multiple lines.


4. Destructuring Assignment vs Traditional Access

Destructuring allows you to unpack values from arrays and objects into variables, reducing repetitive code and enhancing readability.

Define and assign value to variables with the same names as properties:

const person = { name: 'John', age: 30 };
const { name, age } = person;

console.log(name, age);  //Output --> John 30

Enter fullscreen mode Exit fullscreen mode

Define and assign value to variables with different names from properties:

const person = { name: 'John', age: 30 };
const { name:username, age:userage } = person;

console.log(username, userage);  //Output --> John 30
Enter fullscreen mode Exit fullscreen mode

Same way before ES6

var person = { name: 'John', age: 30 };
var name = person.name;
var age = person.age;

console.log(name, age);  //Output --> John 30

Enter fullscreen mode Exit fullscreen mode

Array Destructuring Assign values from an array to different variables:

var arr = [1,2,3];
var [a,b,c] = arr;
console.log(a); //-->1
console.log(b); //-->2
console.log(c); //-->3
Enter fullscreen mode Exit fullscreen mode

Best Practice: Use destructuring for cleaner and more intuitive access to properties from arrays and objects.


5. Default Parameters vs Fallback Logic

ES5 required manual fallback logic to handle missing function arguments, while ES6 introduced default parameters to define fallback values directly in the function signature.

ES5:

function myFunction(x, y) {
  var y = y || 10;
  return x + y;
}
myFunction(5); // will return 15

Enter fullscreen mode Exit fullscreen mode

ES6:

function myFunction(x, y = 10) {
  // y is 10 if not passed or undefined
  return x + y;
}
myFunction(5); //Output --> will return 15

Enter fullscreen mode Exit fullscreen mode

Best Practice: Use default parameters to handle optional function arguments cleanly.


6. Spread Operator vs concat() or apply()

The spread operator (...) allows for simpler merging of arrays and objects and is much more intuitive than using concat() or apply().

ES5:

var arr1 = [1, 2];
var arr2 = [3, 4];
var combined = arr1.concat(arr2);

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

Enter fullscreen mode Exit fullscreen mode

ES6:

const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2];

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

Enter fullscreen mode Exit fullscreen mode

A spread operator would break down an array into values so that they can be easily used:

let nums = [4,5,6];
let nums2 = [1,2,3,...nums,7,8];
console.log(nums2);
    //--> [1,2,3,4,5,6,7,8]
Enter fullscreen mode Exit fullscreen mode

Spread operator is commonly used when a function doesn’t accept an array as a parameter:

function sumValues(a,b,c){
     console.log(arguments);  //print out an array of the arguments of the function
return a+b+c;
}
let nums = [2,3,4];
sumValues(...nums); //values 2,3,4 of nums array has been passed to a,b,c parameters
    //-->[2,3,4]
    //-->9
sumValues(5,5,...nums); //value 2 of nums array has been passed to c parameter
    //-->[5,5,2,3,4]
    //-->12
Enter fullscreen mode Exit fullscreen mode

Another example

let nums = [1,2,3,4];
Math.min(nums);
    //--> NaN
Math.min(...nums);
    //-->1
Enter fullscreen mode Exit fullscreen mode

Best Practice: Use the spread operator for array concatenation, cloning objects, and passing variable arguments into functions.


7. Promises vs Callbacks

In ES5, asynchronous operations were typically handled with callbacks, leading to complex "callback hell" situations. ES6 introduced Promises, which simplify async code.

ES5:

function fetchData(callback) {
  setTimeout(function() {
    callback('Data loaded');
  }, 1000);
}

fetchData(function(data) {
  console.log(data);  // Data loaded
});

Enter fullscreen mode Exit fullscreen mode

ES6:

function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve('Data loaded'), 1000);
  });
}

fetchData().then(data => console.log(data));  // Data loaded

Enter fullscreen mode Exit fullscreen mode

Best Practice: Use Promises (and async/await in modern code) for asynchronous code, as they provide a cleaner and more manageable approach.


8. Classes vs Constructor Functions

ES6 introduced class syntax as syntactic sugar over constructor functions for object-oriented programming. This provides a cleaner, more intuitive way to define and inherit from classes.

ES5:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  return 'Hello, I am ' + this.name;
};

var john = new Person('John', 30);
console.log(john.greet());  // Hello, I am John

Enter fullscreen mode Exit fullscreen mode

ES6:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    return `Hello, I am ${this.name}`;
  }
}

const john = new Person('John', 30);
console.log(john.greet());  // Hello, I am John

Enter fullscreen mode Exit fullscreen mode

Best Practice: Use classes to handle object creation and inheritance when working with OOP patterns in JavaScript.


9. Modules (import/export) vs IIFE or Global Functions

Prior to ES6, JavaScript did not have native module support. Developers had to use Immediately Invoked Function Expressions (IIFE) or rely on global variables. ES6 introduced import and export, allowing modular code organization.

ES5 (IIFE):

(function() {
  function add(x, y) {
    return x + y;
  }
  window.add = add;
})();

console.log(add(2, 3));  // 5

Enter fullscreen mode Exit fullscreen mode

ES6 (Modules):

// math.js
export function add(x, y) {
  return x + y;
}

// main.js
import { add } from './math.js';
console.log(add(2, 3));  // 5

Enter fullscreen mode Exit fullscreen mode

Best Practice: Use ES6 modules for better code organization, reusability, and easier dependency management.


10. Async/await

Async

The ansync keyword that placed before a function makes that the function behave like a Promise:

async function myFunc(){
    return "this is a promise";
}
myFunc().then((val)=>{console.log(val)});
    //-->"this is a promise"
Enter fullscreen mode Exit fullscreen mode

In an async function, the return keyword will act like the resolve keyword in a Promise, the throw keyword will act like the reject keyword in a Promise

async function doHomework(){
    let isDone = false;
    if (isDone){
        return("is done");
    }else{
        throw "is not done";
    }
}
doHomework().then(function(homeworkResult){
    console.log("The homework " + homeworkResult);
}).catch(function(homeworkResult){
    console.log("The homework " + homeworkResult);
})
    //"The homework is not done"
Enter fullscreen mode Exit fullscreen mode

Await

The await keyword is ONLY used in the async function. The await keyword makes your code wait until the Promise inside the function has been fulfilled/rejected:

async function myFunc(){
    let myPromise = new Promise((resolve,reject)=>{
        setTimeout(()=>{resolve("done!")},1000)
    });
    let result = await myPromise; //wait for this promise before continuing
    return result;
}
myFunc().then((result)=>{console.log(result)})

Enter fullscreen mode Exit fullscreen mode

Final Thoughts

ES6 has drastically improved the way JavaScript is written and maintained. Adopting these tips and practices in your daily workflow will not only make your code cleaner but also more maintainable and scalable. Whether you're switching from ES5 or enhancing your ES6 skills, these tricks will help you stay productive.

Contributions Welcome! If you have additional tips, tricks, or use cases from your experience, feel free to share them in the comments.

Happy coding!

💖 💪 🙅 🚩
dilipmishra
Dilip Mishra

Posted on September 21, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related