Learnwizy Technologies Logo

Learnwizy Technologies

Class 14: ES6+ Features & Modern JavaScript

JavaScript is a constantly evolving language. ECMAScript 2015, often referred to as ES6, introduced a significant set of new features and syntax enhancements that revolutionized how JavaScript is written. Subsequent versions (ES7, ES8, ES9, etc., collectively ES6+) have continued to add powerful capabilities. In this class, we will explore some of the most important and frequently used modern JavaScript features that are essential for writing clean, efficient, and expressive code today.


Arrow Functions

Arrow functions (=>) provide a more concise syntax for writing function expressions. They also handle the this keyword differently than traditional functions, lexically binding this (i.e., this refers to the this value of the enclosing execution context).

Basic Syntax

//Traditional function expression
const addOld = function (a, b) {
    return a + b;
};
console.log(addOld(2, 3));

//Arrow function equivalent
const add = (a, b) => a + b; //Implicit return for single expression
console.log(add(2, 3));

//Arrow function with no parameters
const greet = () => "Hello, World!";
console.log(greet());

//Arrow function with multiple statements (requires curly braces and explicit return)
const operate = (x, y) => {
    let sum = x + y;
    let product = x * y;
    return { sum, product };
};
console.log(operate(10, 5));

Destructuring Assignment (Array and Object)

Destructuring assignment is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

Array Destructuring

// Array Destructuring
const colors = ["red", "green", "blue"];

// Traditional way
// const c1 = colors[0];
// const c2 = colors[1];

// Array Destructuring
const [firstColor, secondColor, thirdColor] = colors;
console.log(firstColor);
console.log(secondColor);

// Skipping elements
const [, , lastColor] = colors;
console.log(lastColor);

// With default values
const [a, b, c, d = "purple"] = ["alpha", "beta", "gamma"];
console.log(a, b, c, d);

// Swapping variables easily
let x = 10;
let y = 20;
[x, y] = [y, x];
console.log(x, y);

Object Destructuring

// Object Destructuring
const person = {
    name: "Alice",
    age: 30,
    city: "New York",
};

// Traditional way
// const name = person.name;
// const age = person.age;

const { name, age } = person;
console.log(name);
console.log(age);

// Renaming variables
const { city: town } = person;
console.log(town);

// With default values
const { job = "Unemployed", age: personAge } = person;
console.log(job);
console.log(personAge);

// Nested object destructuring
const user = {
    id: 1,
    details: {
    firstName: "Bob",
    lastName: "Smith",
    },
};

const {
    details: { firstName, lastName },
} = user;
console.log(firstName, lastName);

Spread and Rest Operators (...)

The ... syntax serves two distinct but related purposes: the Spread operator and the Rest parameter.

Spread Operator (...)

Expands an iterable (like an array or string) into individual elements, or an object into key-value pairs, where multiple elements/properties are expected.

// Spreading Arrays
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
console.log(arr2);

const combined = [...arr1, ...[6, 7]]; // Concatenate arrays
console.log(combined);

function sum(a, b, c) {
    return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers));

// Spreading Objects
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 };
console.log(obj2);

const obj3 = { b: 5, d: 4 };
const merged = { ...obj1, ...obj3 };
console.log(merged);

Rest Parameter (...)

Collects an indefinite number of arguments into an array. It must be the last parameter in a function definition.

// Rest Parameter (...)
function calculateAverage(name, ...grades) {
    // 'grades' collects all remaining arguments into an array
    const total = grades.reduce((sum, grade) => sum + grade, 0);
    return `${name}'s average: ${total / grades.length}`;
}

console.log(calculateAverage("John", 90, 85, 92, 88));
console.log(calculateAverage("Jane", 70, 75));

// Rest in destructuring
const [first, second, ...restOfColors] = [
    "red",
    "green",
    "blue",
    "yellow",
    "purple",
];
console.log(first);
console.log(second);
console.log(restOfColors);

Template Literals

Template literals (backticks ``) offer an easy way to create multiline strings and perform string interpolation.

// Template Literals
const productName = "Laptop";
const price = 1200;
const quantity = 2;

// Old way of concatenation
const oldSummary =
    "Product: " +
    productName +
    "\n" +
    "Price: $" +
    price +
    "\n" +
    "Total: $" +
    price * quantity;
console.log(oldSummary);

// Using Template Literals
const newSummary = `Product: ${productName}
Price: $${price}
Total: $${price * quantity}`;
console.log(newSummary);

JavaScript Modules (import, export)

ES Modules provide a standardized way to organize JavaScript code into reusable modules. This helps in breaking down large applications into smaller, manageable files, improving code organization, reusability, and maintainability.

Typically, module files are saved with a .js extension, and the script type in HTML must be type="module".

math.js (Module to be exported)

<!-- math.js -->
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export function subtract(a, b) {
  return a - b;
}

<!-- Exporting a default (only one default export per module) -->
const multiply = (a, b) => a * b;
export default multiply;

app.js (Module consuming math.js)

<!-- app.js -->
import { PI, add, subtract } from './math.js'; <!-- Named imports -->
import multiplyNumbers from './math.js';    <!-- Default import (can be named anything) -->

console.log("PI:", PI);
console.log("2 + 3 =", add(2, 3));
console.log("10 - 4 =", subtract(10, 4));
console.log("5 * 6 =", multiplyNumbers(5, 6));

In HTML (how to include modules)

<!-- index.html -->
<script type="module" src="app.js"></script>

Using modules helps manage dependencies, avoids global variable pollution, and enables tools to optimize your code (e.g., tree-shaking).


Introduction to OOP Principles

JavaScript is a multi-paradigm language, supporting both functional and object-oriented programming (OOP). OOP is a programming paradigm based on the concept of "objects", which can contain data and code.

Objects and Prototypes

JavaScript is a prototype-based language. Instead of traditional class-based inheritance, objects inherit properties and methods from other objects via a prototype chain. Every JavaScript object has a prototype.

// Object literal
const car = {
  brand: "Toyota",
  model: "Camry",
  start: function() {
    console.log(`${this.brand} ${this.model} started.`);
  }
};
car.start();

// Prototype chain example
const animal = {
  eats: true,
  walk() {
    console.log("Animal walks.");
  }
};

const rabbit = {
  jumps: true,
  __proto__: animal // rabbit inherits from animal
};

rabbit.walk();
console.log(rabbit.eats);

Constructor Functions

Before ES6 classes, constructor functions were the primary way to create "classes" or blueprints for objects in JavaScript. They are regular functions that are invoked with the new keyword.

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greet = function() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  };
}

const person1 = new Person("Alice", 30);
const person2 = new Person("Bob", 25);

person1.greet();
person2.greet();

While functional, a common optimization was to add methods to the constructor's prototype to avoid creating a new function for every instance, saving memory.

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

PersonOptimized.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

const person3 = new PersonOptimized("Charlie", 40);
person3.greet();

ES6 Classes

ES6 introduced the class keyword, which is syntactic sugar over JavaScript's existing prototype-based inheritance. It provides a cleaner and more familiar syntax for creating objects and handling inheritance, making JavaScript OOP more approachable for developers from class-based languages.

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

const dog = new Animal("Dog");
dog.speak();

Inheritance with Classes

The extends keyword is used to create a subclass that inherits from a parent class. The super() keyword is used within the subclass's constructor to call the parent class's constructor, ensuring proper initialization of inherited properties.

class Dog extends Animal { // Dog inherits from Animal
  constructor(name, breed) {
    super(name); // Call the parent (Animal) constructor
    this.breed = breed;
  }

  // Override the speak method
  speak() {
    console.log(`${this.name} barks! It's a ${this.breed}.`);
  }

  fetch() {
    console.log(`${this.name} is fetching the ball.`);
  }
}

const myDog = new Dog("Buddy", "Golden Retriever");
myDog.speak();
myDog.fetch();
console.log(myDog.name);

Static Methods and Properties

Static methods and properties belong to the class itself, rather than to any instance of the class. They are called directly on the class name and are often used for utility functions or properties that are relevant to the class as a whole, not a specific object.

class Calculator {
  static PI = 3.14159; // Static property (ES2022)

  static add(a, b) {
    return a + b;
  }

  static subtract(a, b) {
    return a - b;
  }
}

console.log(Calculator.PI);
console.log(Calculator.add(10, 5));
console.log(Calculator.subtract(10, 5));

// Attempting to call a static method on an instance will fail
// const calc = new Calculator();
// calc.add(1, 2); // TypeError: calc.add is not a function

These modern JavaScript features are fundamental for writing clean, efficient, and maintainable code in today's web development landscape, especially when working with frameworks like React.