Skip to content

ES6+ Features

ECMAScript 2015 (ES6) and later versions introduced many powerful features that modernized JavaScript. This guide covers the most important additions.

What You'll Learn

  • Understand let, const vs var
  • Use template literals for cleaner strings
  • Master arrow functions and their this behavior
  • Destructure arrays and objects efficiently
  • Work with spread/rest operators
  • Organize code with ES6 modules
  • Use modern data structures (Map, Set)

ES6+ Features Timeline

┌─────────────────────────────────────────────────────────────────┐
│                    JavaScript Evolution                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ES6 (2015)          ES2017           ES2020          ES2022    │
│  ─────────           ──────           ──────          ──────    │
│  • let/const         • async/await    • ?.            • at()    │
│  • Arrow =>          • Object.entries • ??            • #private│
│  • Classes           • String padding • BigInt        • hasOwn  │
│  • Template ``       • Trailing ,     • Promise.all   • cause   │
│  • Destructuring                        Settled                  │
│  • Modules                                                       │
│  • Promises                                                      │
│  • Map/Set                                                       │
│                                                                  │
│  ──────────────────────────────────────────────────────────────│
│  ES5 (2009)                                       ES2024+       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

let and const

Block-scoped variable declarations.

var vs let vs const

Featurevarletconst
ScopeFunctionBlockBlock
HoistingYes (undefined)Yes (TDZ)Yes (TDZ)
ReassignYesYesNo
RedeclareYesNoNo
Use caseLegacyChanging valuesConstants
js
// let - can be reassigned
let count = 0;
count = 1;

// const - cannot be reassigned
const PI = 3.14159;
// PI = 3; // Error!

// const with objects - properties can change
const user = { name: "John" };
user.name = "Jane"; // OK (changing property)
// user = {}; // Error! (reassigning variable)

// Block scope
if (true) {
    let blockVar = "I'm block scoped";
    const alsoBlock = "Me too";
}
// console.log(blockVar); // Error: not defined
┌─────────────────────────────────────────────────────────────────┐
│                    Scope Comparison                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  function example() {                                            │
│      if (true) {                                                 │
│          var x = 1;    // Function scope (accessible outside)   │
│          let y = 2;    // Block scope (only here)               │
│          const z = 3;  // Block scope (only here)               │
│      }                                                           │
│      console.log(x);   // 1 ✓                                   │
│      console.log(y);   // ReferenceError ✗                      │
│      console.log(z);   // ReferenceError ✗                      │
│  }                                                               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Template Literals

Enhanced strings with backticks.

┌─────────────────────────────────────────────────────────────────┐
│               Old Strings vs Template Literals                   │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  OLD WAY (ES5)                    NEW WAY (ES6+)                 │
│  ─────────────                    ──────────────                 │
│  "Hello " + name + "!"            `Hello ${name}!`               │
│                                                                  │
│  "Line 1\n" +                     `Line 1                        │
│  "Line 2"                         Line 2`                        │
│                                                                  │
│  "Price: $" + (x * y)             `Price: $${x * y}`             │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
js
const name = "Alice";
const age = 25;

// String interpolation
const greeting = `Hello, ${name}! You are ${age} years old.`;

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

// Expressions in templates
const price = 19.99;
const quantity = 3;
console.log(`Total: $${(price * quantity).toFixed(2)}`);

// Tagged templates
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => {
        return result + str + (values[i] ? `<mark>${values[i]}</mark>` : "");
    }, "");
}

const highlighted = highlight`Welcome ${name}, you have ${5} messages`;
// "Welcome <mark>Alice</mark>, you have <mark>5</mark> messages"

Arrow Functions

Concise function syntax.

Arrow Function Cheat Sheet

js
// Multiple parameters
(a, b) => a + b

// Single parameter (parentheses optional)
x => x * 2

// No parameters
() => console.log("Hi")

// Multiple lines (need braces and return)
(x) => {
    const doubled = x * 2;
    return doubled;
}

// Return object (wrap in parentheses!)
(x) => ({ value: x })
js
// Traditional function
function add(a, b) {
    return a + b;
}

// Arrow function
const addArrow = (a, b) => a + b;

// With single parameter
const double = n => n * 2;

// With body block
const greet = name => {
    const greeting = `Hello, ${name}!`;
    return greeting;
};

// Returning objects
const createUser = (name, age) => ({ name, age });

// Arrow functions don't have their own 'this'
const counter = {
    count: 0,
    increment() {
        // Arrow function inherits 'this' from increment
        setInterval(() => {
            this.count++;
            console.log(this.count);
        }, 1000);
    }
};

Destructuring

Extract values from arrays and objects.

┌─────────────────────────────────────────────────────────────────┐
│                 Destructuring Visual Guide                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ARRAY DESTRUCTURING                                             │
│  ───────────────────                                             │
│  const colors = ["red", "green", "blue"];                        │
│                    ↓        ↓        ↓                           │
│  const [first, second, third] = colors;                          │
│         "red"  "green" "blue"                                    │
│                                                                  │
│  OBJECT DESTRUCTURING                                            │
│  ────────────────────                                            │
│  const person = { name: "John", age: 30 };                       │
│                    ↓            ↓                                │
│  const { name, age } = person;                                   │
│         "John"  30                                               │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Array Destructuring

js
const colors = ["red", "green", "blue"];

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

// Skip elements
const [, , last] = colors;
console.log(last); // "blue"

// Default values
const [a, b, c, d = "yellow"] = colors;
console.log(d); // "yellow"

// Rest pattern
const [head, ...tail] = colors;
console.log(tail); // ["green", "blue"]

// Swapping
let x = 1, y = 2;
[x, y] = [y, x];

Object Destructuring

js
const person = {
    name: "John",
    age: 30,
    city: "New York"
};

// Basic
const { name, age } = person;

// Renaming
const { name: fullName, age: years } = person;

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

// Nested
const company = {
    name: "Tech Corp",
    address: {
        street: "123 Main St",
        city: "San Francisco"
    }
};

const { address: { city } } = company;

// In function parameters
function greet({ name, age }) {
    return `Hello ${name}, you are ${age}`;
}
greet(person);

Spread and Rest Operators

The ... operator does different things based on context.

Spread vs Rest

ContextNamePurpose
[...arr]SpreadExpand array elements
{...obj}SpreadExpand object properties
fn(...args)SpreadSpread array as arguments
function(...args)RestCollect arguments into array
const [a, ...rest]RestCollect remaining elements

Spread Operator

Expand iterables:

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

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

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

// Objects
const defaults = { theme: "light", lang: "en" };
const userPrefs = { theme: "dark" };
const settings = { ...defaults, ...userPrefs };
// { theme: "dark", lang: "en" }

Rest Parameters

Collect arguments into an array:

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

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

// With other parameters
function greet(greeting, ...names) {
    return `${greeting}, ${names.join(" and ")}!`;
}

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

Default Parameters

js
function greet(name = "Guest", greeting = "Hello") {
    return `${greeting}, ${name}!`;
}

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

// Using expressions
function createId(prefix = "ID", num = Date.now()) {
    return `${prefix}-${num}`;
}

// Using previous parameters
function createUrl(host, path, protocol = "https") {
    return `${protocol}://${host}${path}`;
}

Enhanced Object Literals

js
const name = "John";
const age = 30;

// Property shorthand
const person = { name, age };

// Method shorthand
const calculator = {
    add(a, b) {
        return a + b;
    },
    subtract(a, b) {
        return a - b;
    }
};

// Computed property names
const prop = "dynamic";
const obj = {
    [prop]: "value",
    [`${prop}Method`]() {
        return "dynamic method";
    }
};

Classes

Syntactic sugar over prototype-based inheritance:

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

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

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

    speak() {
        console.log(`${this.name} barks`);
    }

    // Static method
    static isDog(animal) {
        return animal instanceof Dog;
    }
}

const dog = new Dog("Rex", "German Shepherd");
dog.speak(); // "Rex barks"
Dog.isDog(dog); // true

Modules

Named Exports

js
// math.js
export const PI = 3.14159;
export function add(a, b) {
    return a + b;
}
export function subtract(a, b) {
    return a - b;
}

// main.js
import { PI, add, subtract } from "./math.js";
import { add as sum } from "./math.js"; // Rename
import * as math from "./math.js"; // Import all

Default Exports

js
// user.js
export default class User {
    constructor(name) {
        this.name = name;
    }
}

// main.js
import User from "./user.js";
import MyUser from "./user.js"; // Can use any name

Mixed Exports

js
// utils.js
export const VERSION = "1.0.0";
export function helper() {}
export default function main() {}

// main.js
import main, { VERSION, helper } from "./utils.js";

Symbols

Unique, immutable identifiers:

js
const id = Symbol("id");
const anotherId = Symbol("id");

console.log(id === anotherId); // false (always unique)

// Use as object keys
const user = {
    name: "John",
    [id]: 12345
};

console.log(user[id]); // 12345
console.log(Object.keys(user)); // ["name"] (symbols not included)

// Well-known symbols
const arr = [1, 2, 3];
console.log(arr[Symbol.iterator]); // Iterator function

Iterators and Generators

Iterators

js
const range = {
    start: 1,
    end: 5,
    [Symbol.iterator]() {
        let current = this.start;
        const end = this.end;
        return {
            next() {
                if (current <= end) {
                    return { value: current++, done: false };
                }
                return { done: true };
            }
        };
    }
};

for (const num of range) {
    console.log(num); // 1, 2, 3, 4, 5
}

Generators

js
function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = numberGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

// Infinite generator
function* infiniteSequence() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

// Range generator
function* range(start, end) {
    for (let i = start; i <= end; i++) {
        yield i;
    }
}

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

Map and Set

Modern collection types for specialized use cases.

Object vs Map vs Set

FeatureObjectMapSet
Key typesString/SymbolAny typeN/A (values only)
OrderNot guaranteedInsertion orderInsertion order
SizeManual.size.size
IterationNot directlyYesYes
DuplicatesKeys overwriteKeys overwriteNo duplicates

Map

Key-value pairs with any type of key:

js
const map = new Map();

// Set values
map.set("name", "John");
map.set(1, "one");
map.set({ id: 1 }, "object key");

// Get values
console.log(map.get("name")); // "John"

// Check existence
console.log(map.has("name")); // true

// Size
console.log(map.size); // 3

// Delete
map.delete("name");

// Iteration
map.forEach((value, key) => {
    console.log(`${key}: ${value}`);
});

for (const [key, value] of map) {
    console.log(`${key}: ${value}`);
}

// Initialize with array
const map2 = new Map([
    ["a", 1],
    ["b", 2]
]);

Set

Collection of unique values:

js
const set = new Set();

// Add values
set.add(1);
set.add(2);
set.add(2); // Duplicate, ignored

console.log(set.size); // 2
console.log(set.has(1)); // true

// Delete
set.delete(1);

// Clear all
set.clear();

// Initialize with array
const set2 = new Set([1, 2, 3, 3, 4]); // {1, 2, 3, 4}

// Remove duplicates from array
const arr = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(arr)]; // [1, 2, 3]

// Set operations
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);

const union = new Set([...a, ...b]); // {1, 2, 3, 4}
const intersection = new Set([...a].filter(x => b.has(x))); // {2, 3}
const difference = new Set([...a].filter(x => !b.has(x))); // {1}

Optional Chaining (?.)

Safe property access without checking each level.

┌─────────────────────────────────────────────────────────────────┐
│              Optional Chaining Visual                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  user?.address?.city                                             │
│    │      │      │                                               │
│    ▼      ▼      ▼                                               │
│  exists? → exists? → return value                                │
│    │         │                                                   │
│    └── if null/undefined, return undefined (stop here)          │
│                                                                  │
│  INSTEAD OF:                                                     │
│  user && user.address && user.address.city                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
js
const user = {
    name: "John",
    address: {
        city: "New York"
    }
};

// Without optional chaining
const street = user.address && user.address.street;

// With optional chaining
const street2 = user.address?.street; // undefined

// Method calls
const result = user.getAddress?.(); // undefined if method doesn't exist

// Array access
const first = users?.[0]?.name;

// Combining with nullish coalescing
const city = user.address?.city ?? "Unknown";

Nullish Coalescing (??)

Returns right side only for null or undefined.

?? vs ||

Expression??||
null ?? "default""default""default"
undefined ?? "default""default""default"
0 ?? "default"0"default"
"" ?? "default""""default"
false ?? "default"false"default"

Use ?? when 0, "", or false are valid values!

js
const value1 = null ?? "default";     // "default"
const value2 = undefined ?? "default"; // "default"
const value3 = 0 ?? "default";        // 0
const value4 = "" ?? "default";       // ""
const value5 = false ?? "default";    // false

// Compare with ||
const value6 = 0 || "default";        // "default" (0 is falsy)
const value7 = "" || "default";       // "default" ("" is falsy)

Numeric Separators

js
const billion = 1_000_000_000;
const bytes = 0xFF_FF_FF_FF;
const binary = 0b1010_0001_1000_0101;

Private Class Fields

js
class BankAccount {
    #balance = 0; // Private field

    constructor(initialBalance) {
        this.#balance = initialBalance;
    }

    deposit(amount) {
        this.#balance += amount;
    }

    getBalance() {
        return this.#balance;
    }

    // Private method
    #validateAmount(amount) {
        return amount > 0;
    }
}

const account = new BankAccount(100);
// console.log(account.#balance); // SyntaxError
console.log(account.getBalance()); // 100

Array Methods (ES2019+)

js
// flat and flatMap
const nested = [1, [2, [3, [4]]]];
console.log(nested.flat(2)); // [1, 2, 3, [4]]

// Array.from with mapFn
const doubled = Array.from([1, 2, 3], x => x * 2);

// at() - negative indexing
const arr = [1, 2, 3, 4, 5];
console.log(arr.at(-1)); // 5
console.log(arr.at(-2)); // 4

// findLast and findLastIndex
const nums = [1, 2, 3, 4, 3, 2, 1];
console.log(nums.findLast(n => n > 2)); // 3
console.log(nums.findLastIndex(n => n > 2)); // 4

// toSorted, toReversed, toSpliced (immutable)
const original = [3, 1, 2];
const sorted = original.toSorted(); // [1, 2, 3]
console.log(original); // [3, 1, 2] (unchanged)

Object Methods (ES2017+)

js
const obj = { a: 1, b: 2, c: 3 };

// Object.entries
Object.entries(obj); // [["a", 1], ["b", 2], ["c", 3]]

// Object.fromEntries
Object.fromEntries([["a", 1], ["b", 2]]); // { a: 1, b: 2 }

// Object.values
Object.values(obj); // [1, 2, 3]

// Object.hasOwn (ES2022)
Object.hasOwn(obj, "a"); // true

Exercises

Exercise 1: Data Transformation

Transform an array of objects using modern JavaScript features.

Solution
js
const users = [
    { id: 1, name: "Alice", age: 25, active: true },
    { id: 2, name: "Bob", age: 17, active: true },
    { id: 3, name: "Charlie", age: 30, active: false }
];

const result = users
    .filter(({ active, age }) => active && age >= 18)
    .map(({ id, name }) => ({ id, name, status: "adult" }));

console.log(result);
// [{ id: 1, name: "Alice", status: "adult" }]

Exercise 2: Config Merger

Create a function that deeply merges configuration objects.

Solution
js
const deepMerge = (target, source) => {
    const result = { ...target };

    for (const [key, value] of Object.entries(source)) {
        result[key] = value && typeof value === "object" && !Array.isArray(value)
            ? deepMerge(result[key] ?? {}, value)
            : value;
    }

    return result;
};

const defaults = {
    theme: { primary: "blue", secondary: "gray" },
    features: { darkMode: false }
};

const userConfig = {
    theme: { primary: "red" },
    features: { darkMode: true, beta: true }
};

console.log(deepMerge(defaults, userConfig));
// {
//   theme: { primary: "red", secondary: "gray" },
//   features: { darkMode: true, beta: true }
// }

Quick Reference

ES6+ Cheat Sheet

js
// Variables
const PI = 3.14;           // Can't reassign
let count = 0;             // Can reassign

// Template literals
`Hello ${name}!`           // String interpolation

// Arrow functions
const add = (a, b) => a + b;

// Destructuring
const { name, age } = person;
const [first, ...rest] = array;

// Spread
const merged = [...arr1, ...arr2];
const copy = { ...obj };

// Optional chaining & nullish coalescing
user?.address?.city ?? "Unknown"

// Modules
import { func } from "./module.js";
export const value = 42;

Summary

FeatureWhat It DoesExample
let/constBlock-scoped variablesconst x = 1
Template literalsString interpolation`Hi ${name}`
Arrow functionsConcise functionsx => x * 2
DestructuringExtract valuesconst {a, b} = obj
Spread/RestExpand/collect[...arr], ...args
ClassesOOP syntaxclass Dog extends Animal
ModulesCode organizationimport/export
Map/SetSpecialized collectionsnew Map(), new Set()
?.Safe property accessobj?.prop
??Nullish defaultx ?? "default"

Next Steps

Continue to Object-Oriented Programming to learn about OOP patterns in JavaScript.