Modern JavaScript Features
Modern JavaScript Features
JavaScript evolves yearly through the TC39 process. This lesson covers several features that sharpen your daily coding: the nuances of arrow functions' this binding, template literal tricks, and new data structures like Symbol, WeakMap, and WeakSet.
Arrow Functions & this Binding
The key difference between arrow functions and regular functions is how this is resolved. Arrow functions have no own this — they capture this lexically from their surrounding scope at definition time.
Regular functions receive their own this determined at call time by how the function is invoked.
class Timer {
constructor() { this.ticks = 0; }
start() {
// Arrow: 'this' is the Timer instance (lexical)
setInterval(() => { this.ticks++; }, 1000);
// Regular fn would lose 'this' here
}
}Template Literal Tags
Tagged templates let a function process a template literal:
function highlight(strings, ...values) {
return strings.reduce((acc, str, i) =>
acc + str + (values[i] !== undefined ? `[${values[i]}]` : ""), "");
}
const name = "Alice";
highlight`Hello ${name}!`; // "Hello [Alice]!"Symbol
Symbols are unique, immutable primitive values. Two Symbols are never equal even if given the same description:
const id1 = Symbol("id");
const id2 = Symbol("id");
id1 === id2; // falseUse Symbols as unique object keys that never clash with other code's keys (e.g., library internal state).
WeakMap & WeakSet
Unlike Map and Set, WeakMap and WeakSet hold weak references — their entries do not prevent garbage collection of the key objects.
- WeakMap: keys must be objects; useful for associating metadata with objects without preventing their GC
- WeakSet: stores objects weakly; useful for tracking which objects have been processed
Neither is iterable — you cannot enumerate their contents.
const cache = new WeakMap();
const visited = new WeakSet();Nullish Coalescing & Optional Chaining (quick recap)
Already covered in Section 7 — these two operators (?? and ?.) fundamentally changed how JavaScript handles missing data.
Code Examples
class Counter {
constructor(name) {
this.name = name;
this.count = 0;
}
// Arrow function as class field: 'this' is always the instance
increment = () => {
this.count++;
return this;
};
printLater() {
// Arrow inside a method: captures 'this' from printLater's scope
setTimeout(() => {
console.log(`${this.name}: ${this.count}`);
}, 0);
}
}
const c = new Counter("MyCounter");
c.increment().increment().increment();
c.printLater();
// Contrast: detached regular method loses 'this'
const obj = {
value: 42,
getArrow: () => obj.value, // no own 'this', uses outer scope
getRegular: function() { return this.value; },
};
const detached = obj.getRegular.bind(obj);
console.log(detached()); // 42 (re-bound)Arrow functions defined as class fields (increment = () => {}) always keep this bound to the instance, making them safe to pass as callbacks. Regular methods need .bind() when detached from their object.
// Symbols are always unique
const sym1 = Symbol("id");
const sym2 = Symbol("id");
console.log(sym1 === sym2); // false
console.log(typeof sym1); // symbol
// Using Symbol as a unique object key
const ID = Symbol("id");
const SECRET = Symbol("secret");
const user = {
name: "Alice",
[ID]: 12345,
[SECRET]: "hunter2",
};
console.log(user.name); // Alice
console.log(user[ID]); // 12345
// Symbols are NOT enumerated by for...in or Object.keys
console.log(Object.keys(user)); // ['name']
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id), Symbol(secret)]
// Well-known symbols — customise language behaviour
class EvenNumbers {
constructor(max) { this.max = max; }
[Symbol.iterator]() {
let n = 0;
const max = this.max;
return { next() { n += 2; return n <= max ? { value: n, done: false } : { done: true }; } };
}
}
console.log([...new EvenNumbers(10)]);Symbols make truly unique property keys invisible to normal enumeration. Well-known Symbols like Symbol.iterator let you integrate custom objects with built-in JavaScript protocols such as the spread operator and for...of.
// WeakMap: attach private data to objects without preventing GC
const privateData = new WeakMap();
class User {
constructor(name, password) {
this.name = name;
privateData.set(this, { password });
}
checkPassword(attempt) {
return privateData.get(this).password === attempt;
}
}
const alice = new User("Alice", "s3cr3t");
console.log(alice.checkPassword("wrong")); // false
console.log(alice.checkPassword("s3cr3t")); // true
console.log(alice.password); // undefined — not on instance
// WeakSet: track visited objects
const seen = new WeakSet();
function processOnce(obj) {
if (seen.has(obj)) {
console.log("Already processed:", obj.id);
return;
}
seen.add(obj);
console.log("Processing:", obj.id);
}
const task = { id: "task-1" };
processOnce(task);
processOnce(task);WeakMap stores metadata (like passwords) keyed by object reference without preventing GC when the object is no longer needed. WeakSet efficiently tracks which objects have been processed without holding strong references.
Quick Quiz
1. What is the key difference between arrow functions and regular functions regarding 'this'?
2. Are two Symbol('desc') values created separately equal to each other?
3. Why are WeakMap keys required to be objects?
Was this lesson helpful?