07 - Modern JavaScript (ES6+)

Template Literals

Create strings with embedded expressions:

// Old way
const name = "Alice";
const age = 25;
const message = "Hello, " + name + "! You are " + age + " years old.";

// Template literals (ES6+)
const message = `Hello, ${name}! You are ${age} years old.`;

// Multi-line strings
const html = `
  <div class="card">
    <h2>${name}</h2>
    <p>Age: ${age}</p>
  </div>
`;

// Expressions inside ${}
const price = 19.99;
const tax = 0.08;
const total = `Total: $${(price * (1 + tax)).toFixed(2)}`;
console.log(total);  // "Total: $21.59"

Destructuring

Extract values from arrays and objects:

// Array destructuring
const colors = ["red", "green", "blue"];
const [first, second, third] = colors;
console.log(first);  // "red"

// Object destructuring
const person = { name: "Alice", age: 25, city: "NYC" };
const { name, age } = person;
console.log(name);  // "Alice"

// Rename during destructuring
const { name: personName, age: personAge } = person;

// Default values
const { name, country = "USA" } = person;

// Nested destructuring
const user = {
  id: 1,
  profile: {
    name: "Alice",
    email: "alice@example.com"
  }
};

const { profile: { name, email } } = user;
console.log(name);  // "Alice"

// Function parameters
function greet({ name, age }) {
  console.log(`Hello ${name}, you are ${age}`);
}

greet(person);  // "Hello Alice, you are 25"

Spread Operator

Expand arrays and objects:

// Arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// Combine arrays
const combined = [...arr1, ...arr2];
console.log(combined);  // [1, 2, 3, 4, 5, 6]

// Copy array
const copy = [...arr1];

// Add items
const extended = [0, ...arr1, 4];
console.log(extended);  // [0, 1, 2, 3, 4]

// Objects
const person = { name: "Alice", age: 25 };
const address = { city: "NYC", country: "USA" };

// Merge objects
const full = { ...person, ...address };
console.log(full);
// { name: "Alice", age: 25, city: "NYC", country: "USA" }

// Override properties
const updated = { ...person, age: 26 };
console.log(updated);  // { name: "Alice", age: 26 }

// Function arguments
const numbers = [1, 5, 3, 9, 2];
console.log(Math.max(...numbers));  // 9

Rest Operator

Collect multiple elements into an array:

// Function parameters
function sum(...numbers) {
  return numbers.reduce((total, n) => total + n, 0);
}

console.log(sum(1, 2, 3));        // 6
console.log(sum(1, 2, 3, 4, 5));  // 15

// Mix with regular parameters
function introduce(greeting, ...names) {
  console.log(`${greeting} ${names.join(", ")}`);
}

introduce("Hello", "Alice", "Bob", "Charlie");
// "Hello Alice, Bob, Charlie"

// Array destructuring
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first);  // 1
console.log(second); // 2
console.log(rest);   // [3, 4, 5]

// Object destructuring
const person = { name: "Alice", age: 25, city: "NYC", country: "USA" };
const { name, ...details } = person;
console.log(name);     // "Alice"
console.log(details);  // { age: 25, city: "NYC", country: "USA" }

Arrow Functions

Concise function syntax:

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

// Arrow function
const add = (a, b) => {
  return a + b;
};

// Implicit return (no braces)
const add = (a, b) => a + b;

// Single parameter (no parentheses needed)
const double = n => n * 2;

// No parameters (parentheses required)
const greet = () => "Hello!";

// Returning object literal (wrap in parentheses)
const makePerson = (name, age) => ({ name, age });

Default Parameters

// Old way
function greet(name) {
  name = name || "Guest";
  return `Hello, ${name}!`;
}

// Modern way
function greet(name = "Guest") {
  return `Hello, ${name}!`;
}

console.log(greet());        // "Hello, Guest!"
console.log(greet("Alice")); // "Hello, Alice!"

// With expressions
function createArray(length = 10, fill = 0) {
  return Array(length).fill(fill);
}

console.log(createArray());      // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
console.log(createArray(3, 5));  // [5, 5, 5]

Enhanced Object Literals

const name = "Alice";
const age = 25;

// Old way
const person = {
  name: name,
  age: age,
  greet: function() {
    console.log("Hello");
  }
};

// Modern way
const person = {
  name,
  age,
  greet() {
    console.log("Hello");
  }
};

// Computed property names
const key = "favoriteColor";
const person = {
  name: "Alice",
  [key]: "blue"
};
console.log(person.favoriteColor);  // "blue"

Classes

Object-oriented programming in JavaScript:

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

  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }

  haveBirthday() {
    this.age++;
    console.log(`Happy birthday! Now ${this.age}`);
  }
}

const alice = new Person("Alice", 25);
alice.greet();         // "Hello, I'm Alice"
alice.haveBirthday();  // "Happy birthday! Now 26"

// Inheritance
class Student extends Person {
  constructor(name, age, major) {
    super(name, age);  // call parent constructor
    this.major = major;
  }

  study() {
    console.log(`${this.name} is studying ${this.major}`);
  }
}

const bob = new Student("Bob", 20, "Computer Science");
bob.greet();   // "Hello, I'm Bob" (inherited)
bob.study();   // "Bob is studying Computer Science"

// Static methods
class MathUtils {
  static add(a, b) {
    return a + b;
  }

  static multiply(a, b) {
    return a * b;
  }
}

console.log(MathUtils.add(2, 3));      // 5
console.log(MathUtils.multiply(4, 5)); // 20

// Getters and setters
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  get area() {
    return this.width * this.height;
  }

  set area(value) {
    this.width = Math.sqrt(value);
    this.height = Math.sqrt(value);
  }
}

const rect = new Rectangle(10, 5);
console.log(rect.area);  // 50
rect.area = 100;
console.log(rect.width); // 10

Modules

Split code across files:

// math.js — export functions
export function add(a, b) {
  return a + b;
}

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

export const PI = 3.14159;

// Default export
export default function multiply(a, b) {
  return a * b;
}

// main.js — import functions
import multiply, { add, subtract, PI } from "./math.js";

console.log(add(2, 3));       // 5
console.log(multiply(2, 3));  // 6
console.log(PI);              // 3.14159

// Import everything
import * as MathUtils from "./math.js";
console.log(MathUtils.add(2, 3));       // 5
console.log(MathUtils.default(2, 3));   // 6

// Rename imports
import { add as sum } from "./math.js";
console.log(sum(2, 3));  // 5

Optional Chaining

Safely access nested properties:

const user = {
  name: "Alice",
  address: {
    city: "NYC"
  }
};

// Old way — manual checks
const country = user && user.address && user.address.country;

// Optional chaining
const country = user?.address?.country;  // undefined (no error)
const city = user?.address?.city;        // "NYC"

// With arrays
const users = [{ name: "Alice" }];
const secondUser = users?.[1]?.name;  // undefined (no error)

// With functions
const result = user.someMethod?.();  // undefined if method doesn't exist

Nullish Coalescing

Provide default values for null/undefined:

// Old way — ||
const value = someValue || "default";
// Problem: 0, "", false are all replaced

// Nullish coalescing — ??
const value = someValue ?? "default";
// Only replaces null and undefined

const count = 0;
console.log(count || 10);   // 10 (0 is falsy)
console.log(count ?? 10);   // 0 (0 is not null/undefined)

const name = "";
console.log(name || "Guest");   // "Guest" ("" is falsy)
console.log(name ?? "Guest");   // "" ("" is not null/undefined)

Array Methods (ES6+)

const numbers = [1, 2, 3, 4, 5];

// find — first matching item
const found = numbers.find(n => n > 3);
console.log(found);  // 4

// findIndex — index of first match
const index = numbers.findIndex(n => n > 3);
console.log(index);  // 3

// includes — check if exists
console.log(numbers.includes(3));  // true

// flat — flatten nested arrays
const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat());     // [1, 2, 3, 4, [5, 6]]
console.log(nested.flat(2));    // [1, 2, 3, 4, 5, 6]

// flatMap — map then flatten
const words = ["hello world", "foo bar"];
const letters = words.flatMap(word => word.split(" "));
console.log(letters);  // ["hello", "world", "foo", "bar"]

Key Takeaways

  • Template literals (`) make string interpolation easy
  • Destructuring extracts values from arrays and objects
  • Spread (...) expands, rest (...) collects
  • Arrow functions provide concise syntax
  • Classes add object-oriented programming
  • Modules split code across files with import/export
  • Optional chaining (?.) safely accesses nested properties
  • Nullish coalescing (??) provides defaults for null/undefined only
  • Modern array methods make data manipulation easier