Core ES6 Foundations
let and const replaced var; template literals replaced string concatenation; arrow functions simplified callbacks.
// let / const β block-scoped
let count = 0;
const MAX = 100; // cannot be reassigned (but object properties can change)
// var is function-scoped and hoisted β avoid it
// Template literals
const name = 'Alice';
const greeting = `Hello, ${name}! You have ${MAX - count} points left.`;
const multiLine = `
Line 1
Line 2
Line 3
`.trim();
// Tagged template literal
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] !== undefined ? `${values[i]}` : ''), '');
}
const html = highlight`Hello ${name}, your score is ${95}!`;
// 'Hello Alice, your score is 95!'
// Arrow functions
const add = (a, b) => a + b;
const square = n => n * n;
const greet = () => 'Hello!';
const getUser = id => ({ id, name: 'Alice' }); // wrap object in ()
// Default parameters
function createUser(name, role = 'user', active = true) {
return { name, role, active };
}
console.log(createUser('Bob')); // { name: 'Bob', role: 'user', active: true }
Enhanced Object Literals
const x = 10, y = 20;
// Shorthand properties
const point = { x, y }; // { x: 10, y: 20 }
// Method shorthand
const obj = {
value: 0,
increment() { this.value++; }, // instead of increment: function() {}
async fetchData() { /* ... */ }, // async method shorthand
get doubled() { return this.value * 2; }, // getter
set doubled(v) { this.value = v / 2; } // setter
};
// Computed property names
const prefix = 'get';
const api = {
[`${prefix}Name`]() { return 'Alice'; },
[`${prefix}Age`]() { return 28; }
};
console.log(api.getName()); // 'Alice'
console.log(api.getAge()); // 28
// for...of loop β works with any iterable
for (const char of 'hello') {
console.log(char); // h e l l o
}
for (const [i, v] of ['a', 'b', 'c'].entries()) {
console.log(i, v); // 0 'a', 1 'b', 2 'c'
}
Symbol and the Iterator Protocol
Symbol creates unique, non-colliding property keys. The iterator protocol lets any object become iterable using Symbol.iterator.
// Symbol β always unique
const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2); // false
// Use as private-ish object key
const ID = Symbol('id');
const user = { [ID]: 42, name: 'Alice' };
console.log(user[ID]); // 42
// Symbol keys don't appear in Object.keys() or JSON.stringify
// Well-known symbols
// Symbol.iterator β makes an object iterable
const range = {
from: 1,
to: 5,
[Symbol.iterator]() {
let current = this.from;
const last = this.to;
return {
next() {
return current <= last
? { value: current++, done: false }
: { value: undefined, done: true };
}
};
}
};
for (const n of range) {
console.log(n); // 1 2 3 4 5
}
console.log([...range]); // [1, 2, 3, 4, 5]
Generators
Generator functions (function*) can pause and resume execution. They are the foundation for async/await and lazy evaluation patterns.
// Generator function
function* count(start, end) {
for (let i = start; i <= end; i++) {
yield i; // pause here, resume on next .next() call
}
}
const gen = count(1, 5);
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log([...count(1, 3)]); // [1, 2, 3]
// Infinite sequence (pull-based β only computes what you ask)
function* naturals() {
let n = 0;
while (true) yield n++;
}
const nat = naturals();
console.log([...Array(5)].map(() => nat.next().value)); // [0,1,2,3,4]
// Generator delegation with yield*
function* combined() {
yield* [1, 2, 3];
yield* 'abc';
}
console.log([...combined()]); // [1, 2, 3, 'a', 'b', 'c']
Proxy (Brief Introduction)
// Proxy intercepts operations on an object
const handler = {
get(target, prop) {
return prop in target ? target[prop] : `Property "${prop}" not found`;
},
set(target, prop, value) {
if (typeof value !== 'number') throw new TypeError('Must be a number');
target[prop] = value;
return true; // indicate success
}
};
const numbers = new Proxy({}, handler);
numbers.x = 10; // OK
// numbers.y = 'hello'; // TypeError: Must be a number
console.log(numbers.x); // 10
console.log(numbers.z); // 'Property "z" not found'
Proxies power Vue 3's reactivity system, input validation libraries, and observable state patterns. Common traps include get, set, has, deleteProperty, and apply (for function proxies).
Modern Syntax: ES2017β2024
// Optional chaining ?. (ES2020)
const user = { profile: { address: { city: 'London' } } };
console.log(user?.profile?.address?.city); // 'London'
console.log(user?.settings?.theme); // undefined (no error)
console.log(user?.getName?.()); // undefined (method may not exist)
// Nullish coalescing ?? (ES2020) β falls back only on null/undefined
const port = null;
console.log(port ?? 3000); // 3000
console.log(port || 3000); // 3000 (same here, but...)
console.log(0 ?? 3000); // 0 (!! zero is NOT nullish)
console.log(0 || 3000); // 3000 (zero is falsy β wrong for port!)
// Logical assignment (ES2021)
let a = null;
a ??= 'default'; // assign only if null/undefined
console.log(a); // 'default'
let b = 0;
b ||= 42; // assign if falsy
console.log(b); // 42
let c = 1;
c &&= 99; // assign if truthy
console.log(c); // 99
// Array.at() (ES2022) β negative indexing
console.log([1,2,3].at(-1)); // 3
// Object.hasOwn() (ES2022) β better hasOwnProperty
console.log(Object.hasOwn({ a: 1 }, 'a')); // true
// structuredClone() (ES2022) β deep clone
const original = { nested: { value: 1 }, arr: [1, 2, 3] };
const clone = structuredClone(original);
clone.nested.value = 99;
console.log(original.nested.value); // 1 β truly independent
// Array grouping (ES2024 β Object.groupBy)
const items = [
{ name: 'a', type: 'x' }, { name: 'b', type: 'y' }, { name: 'c', type: 'x' }
];
const grouped = Object.groupBy(items, item => item.type);
// { x: [{name:'a',...},{name:'c',...}], y: [{name:'b',...}] }
London undefined 3000 0 'default' 42 99 3 true 1
| Feature | Version | Summary |
|---|---|---|
let/const, arrow functions, classes, modules | ES2015 | The big overhaul |
async/await, Object.entries, Object.values, String.padStart/End | ES2017 | Async + object utilities |
Optional catch binding, Array.flat, Array.flatMap | ES2019 | Error & array improvements |
Optional chaining ?., nullish coalescing ??, Promise.allSettled | ES2020 | Safety operators |
Logical assignment ??= ||= &&=, WeakRef | ES2021 | Assignment shortcuts |
Array.at(), Object.hasOwn(), structuredClone(), top-level await | ES2022 | Convenience methods |
Array.findLast/findLastIndex, toSorted/toReversed/toSpliced/with | ES2023 | Immutable array methods |
Object.groupBy, Map.groupBy, Promise.withResolvers | ES2024 | Grouping & promise utilities |
Prefer ?? over || when the default should only kick in for null/undefined, not for other falsy values like 0, '', or false. This is especially important for port numbers, counts, and booleans.
Interview Questions
- What is the difference between
??and||? - What makes a function a generator, and how do you consume it?
- What is optional chaining and when would you use it?
- What is the Symbol type used for?
- What are the non-mutating array methods introduced in ES2023?
ποΈ Practical Exercise
Create a range(start, end, step = 1) generator function that yields every number from start up to (but not including) end, incrementing by step. It should behave exactly like Python's range(). Test it with [...range(0, 10, 2)] (should give [0, 2, 4, 6, 8]).
π₯ Challenge Exercise
Build a reactive observable object using Proxy. It should accept an initial state object and a callback. Whenever any property is set, the callback should be called with (key, oldValue, newValue). Then extend it to support nested objects reactively (so setting state.user.name = 'Bob' also triggers the callback).
Frequently Asked Questions
- Should I still use
var? - No. Use
constby default; useletwhen the value needs to be reassigned.varis function-scoped, hoisted, and lacks temporal dead zone β these quirks cause subtle bugs. Modern code should exclusively useconstandlet. - What is the temporal dead zone?
- Variables declared with
letandconstexist from the start of their block scope but cannot be accessed before their declaration line. Attempting to read them before the declaration throws a ReferenceError. This prevents the confusing "undefined before declaration" behaviour ofvar. - When should I use a generator instead of an array?
- Use generators for lazy, potentially infinite sequences or when the full sequence is expensive to compute. Generators produce values on demand β only as many as you consume β saving memory compared to materialising an entire array up front.
- Is optional chaining safe for function calls?
- Yes.
obj?.method?.()will not throw ifobjis nullish or ifmethoddoes not exist β it simply returnsundefined. Be careful not to silently swallow real errors; use it only where the property genuinely may be absent. - What is the difference between
structuredCloneandJSON.parse(JSON.stringify(x))? structuredClonehandles Dates, RegExp, Map, Set, ArrayBuffer, circular references, and more. JSON round-trip silently drops functions,undefined, and Symbols, and converts Date objects to strings. UsestructuredClonefor reliable deep cloning.
π Summary
- Use
constby default,letfor reassignable variables, nevervar. - Template literals support multi-line strings, expressions, and tagged templates.
- Arrow functions capture the surrounding
thisβ great for callbacks, not for methods. - Enhanced object literals include shorthand properties, method shorthand, and computed keys.
- Symbol creates unique identifiers for use as "private" or well-known keys.
- Generators (
function*+yield) enable lazy, pull-based iteration. - Optional chaining
?.and nullish coalescing??are essential for safe property access. - Use
??=instead of||=when the default should only apply tonull/undefined.