Key ES6 features that changed JavaScript forever

ES6 is a new version of JavaScript that supports many new features, such as arrow functions, destructuring, classes, and let and const. It was released in 2015, and is also known as ES2015 or ECMAScript 6. ES6 makes JavaScript more expressive, concise, and powerful. It also helps developers to write cleaner and more maintainable code.

Arrow Functions

An arrow function is a concise way of writing a function expression in JavaScript. It uses a fat arrow (=>) to separate the parameter(s) from the body. They do not bind their own this, arguments, or super keywords, which can be useful in some contexts. They cannot be used as constructors or generators, which can prevent some errors.

// Traditional function expression
const add = function(a, b) {
  return a + b;
};

// Equivalent arrow function
const add = (a, b) => a + b;

Template Strings

Template strings are a way of creating strings that can include variables, expressions, and multi-line text. They are enclosed by backticks instead of single or double quotes.

let name = "Alice";
let age = 25;
let message = `Hello, my name is ${name} and I am ${age} years old.`;
console.log(message); // Hello, my name is Alice and I am 25 years old.

Destructuring Assignment

Destructuring is a feature that allows you to extract values from arrays or properties from objects into distinct variables, making it more concise and readable. It provides a convenient way to assign values to variables from complex data structures.

// Array destructuring
const numbers = [1, 2, 3];
const [a, b, c] = numbers;

// Object destructuring
const person = {
  firstName: 'John',
  lastName: 'Doe',
  age: 30,
};
const { firstName, lastName, age } = person;

// Object destructuring in function parameters
const printPersonInfo = ({ firstName, lastName, age }) => {
  console.log(`${firstName} ${lastName}, ${age} years old`);
};

Spread Operator

The spread operator is a syntax that allows you to expand the elements of an iterable (such as an array or a string) into places where multiple arguments or elements are expected. It is denoted by three dots ... followed by an expression or an iterable. Very useful for copying, merging, or transforming iterables in JavaScript.

// Spread an array into function arguments
const numbers = [1, 2, 3];
Math.max(...numbers); // returns 6

// Spread a string into an array of characters
const name = "Alice";
const chars = [...name]; // returns ["A", "l", "i", "c", "e"]

// Spread an object into another object
const person = {name: "Bob", age: 25};
const employee = {...person, salary: 5000}; // returns {name: "Bob", age: 25, salary: 5000}

Rest Parameters

Rest parameters are a way to accept a variable number of arguments in a function, written as three dots ... followed by a parameter name, which becomes an array containing all the extra arguments. They are useful for creating variadic functions, which can handle different numbers and types of inputs.

// A function that sums all the arguments
function sum(...args) {
  let total = 0;
  for (let arg of args) {
    total += arg;
  }
  return total;
}

// Calling the function with different numbers of arguments
console.log(sum(1, 2, 3)); // 6
console.log(sum(4, 5)); // 9

Classes

Classes are a syntactic sugar over the existing prototype-based inheritance mechanism. They provide a cleaner and more elegant way to create objects and implement inheritance. A class is a blueprint for an object, which can have properties (data or methods) and a constructor (a special method for initializing an object).

class Animal {
  // Define a constructor with a parameter name
  constructor(name) {
    // Assign the name to the instance
    this.name = name;
  }

  // Define an instance method named speak
  speak() {
    console.log(this.name + " makes a noise.");
  }
}

let dog = new Animal("Rex");
dog.speak(); // Rex makes a noise.

// Define a subclass named Cat that inherits from Animal
class Cat extends Animal {
  // Override the speak method
  speak() {
    // Call the super class method with super keyword
    super.speak();
    console.log(this.name + " meows.");
  }
}

let kitty = new Cat("Luna");
kitty.speak(); // Luna makes a noise. Luna meows.

Modules

Before ES6, there was no native support for modules in JavaScript. Modules are a way to organize and reuse code across different files. They allow you to define variables, functions, classes, or other objects in one file and export them to be used in another file. They also help you avoid polluting the global scope and creating conflicts with other scripts.

ES6 introduced a new syntax for creating and importing modules, using the import and export keywords. To create a module in ES6, you need to write your code in a separate file with the .js extension, and use the export keyword to expose the objects you want to make available to other files.

// math.js
// Define a constant
export const PI = 3.14;
// Define a function
export function square(x) {
  return x * x;
}
// Define a default export
export default function cube(x) {
  return x * x * x;
}

To import a module in ES6, you need to use the import keyword and specify the path to the module file. You can also use curly braces to import specific named exports, or use an alias to rename them.

// app.js
// Import the default export and the PI constant from math.js
import cube, { PI } from './math.js';
// Import the square function and rename it as sq
import { square as sq } from './math.js';
// Use the imported objects
console.log(cube(3)); // 27
console.log(sq(4)); // 16
console.log(PI); // 3.14

Promises

Promises are objects that represent the eventual outcome of an asynchronous operation. They can be either fulfilled (with a value) or rejected (with a reason). Promises allow you to write code that can handle both success and failure scenarios, without relying on callbacks or nested functions. You can chain promises together using the .then() and .catch() methods, or use the async/await syntax for a more readable approach.

// Creating a promise
let myPromise = new Promise((resolve, reject) => {
  // Some asynchronous operation
  let x = Math.random();
  if (x > 0.5) {
    resolve("Success!"); // The promise is fulfilled with this value
  } else {
    reject("Failure!"); // The promise is rejected with this reason
  }
});

// Using a promise
myPromise
  .then((value) => {
    // This function is called when the promise is fulfilled
    console.log(value); // "Success!"
  })
  .catch((reason) => {
    // This function is called when the promise is rejected
    console.error(reason); // "Failure!"
  });