Appearance
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,constvsvar - Use template literals for cleaner strings
- Master arrow functions and their
thisbehavior - 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
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisting | Yes (undefined) | Yes (TDZ) | Yes (TDZ) |
| Reassign | Yes | Yes | No |
| Redeclare | Yes | No | No |
| Use case | Legacy | Changing values | Constants |
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
| Context | Name | Purpose |
|---|---|---|
[...arr] | Spread | Expand array elements |
{...obj} | Spread | Expand object properties |
fn(...args) | Spread | Spread array as arguments |
function(...args) | Rest | Collect arguments into array |
const [a, ...rest] | Rest | Collect 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); // trueModules
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 allDefault 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 nameMixed 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 functionIterators 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
| Feature | Object | Map | Set |
|---|---|---|---|
| Key types | String/Symbol | Any type | N/A (values only) |
| Order | Not guaranteed | Insertion order | Insertion order |
| Size | Manual | .size | .size |
| Iteration | Not directly | Yes | Yes |
| Duplicates | Keys overwrite | Keys overwrite | No 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()); // 100Array 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"); // trueExercises
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
| Feature | What It Does | Example |
|---|---|---|
let/const | Block-scoped variables | const x = 1 |
| Template literals | String interpolation | `Hi ${name}` |
| Arrow functions | Concise functions | x => x * 2 |
| Destructuring | Extract values | const {a, b} = obj |
| Spread/Rest | Expand/collect | [...arr], ...args |
| Classes | OOP syntax | class Dog extends Animal |
| Modules | Code organization | import/export |
| Map/Set | Specialized collections | new Map(), new Set() |
?. | Safe property access | obj?.prop |
?? | Nullish default | x ?? "default" |
Next Steps
Continue to Object-Oriented Programming to learn about OOP patterns in JavaScript.