Ad – 728Γ—90
πŸ”§ Intermediate JS

JavaScript ES6+ Features – Modern JavaScript Explained

ES2015 (ES6) was the biggest update to JavaScript in its history, and the annual releases since have continued to modernise the language. This lesson tours every significant feature from ES6 through ES2024, so you can read and write modern JavaScript with confidence.

⏱️ 32 min read 🎯 Intermediate πŸ“… Updated 2026

Core ES6 Foundations

let and const replaced var; template literals replaced string concatenation; arrow functions simplified callbacks.

JavaScript
// 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

JavaScript
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.

JavaScript
// 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.

JavaScript
// 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)

JavaScript
// 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'
πŸ’‘
Proxy Use Cases

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

JavaScript
// 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',...}] }
β–Ά Output
London
undefined
3000
0
'default'
42
99
3
true
1
FeatureVersionSummary
let/const, arrow functions, classes, modulesES2015The big overhaul
async/await, Object.entries, Object.values, String.padStart/EndES2017Async + object utilities
Optional catch binding, Array.flat, Array.flatMapES2019Error & array improvements
Optional chaining ?., nullish coalescing ??, Promise.allSettledES2020Safety operators
Logical assignment ??= ||= &&=, WeakRefES2021Assignment shortcuts
Array.at(), Object.hasOwn(), structuredClone(), top-level awaitES2022Convenience methods
Array.findLast/findLastIndex, toSorted/toReversed/toSpliced/withES2023Immutable array methods
Object.groupBy, Map.groupBy, Promise.withResolversES2024Grouping & promise utilities
πŸ’‘
Use Nullish Coalescing for Numeric Defaults

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 const by default; use let when the value needs to be reassigned. var is function-scoped, hoisted, and lacks temporal dead zone β€” these quirks cause subtle bugs. Modern code should exclusively use const and let.
What is the temporal dead zone?
Variables declared with let and const exist 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 of var.
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 if obj is nullish or if method does not exist β€” it simply returns undefined. Be careful not to silently swallow real errors; use it only where the property genuinely may be absent.
What is the difference between structuredClone and JSON.parse(JSON.stringify(x))?
structuredClone handles Dates, RegExp, Map, Set, ArrayBuffer, circular references, and more. JSON round-trip silently drops functions, undefined, and Symbols, and converts Date objects to strings. Use structuredClone for reliable deep cloning.
Ad – 336Γ—280

πŸ“‹ Summary

  • Use const by default, let for reassignable variables, never var.
  • 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 to null/undefined.