ES6+advanced
Well-Known Symbols and Symbol.iterator
The set of built-in Symbol values that let user code hook into and customize core language behaviors like iteration, type coercion, and instanceof checks.
Well-known symbols are the hooks the language itself uses. Defining one changes how a built-in operation treats your object.
Well-known symbols are like universal remote-control buttons built into every device: if an appliance chooses to wire up the volume button to its own custom behavior, the remote automatically uses that wiring instead of doing nothing.
Key Concepts
1
Symbol.iterator for...of, spread, destructuring
Symbol.asyncIterator for await...of
Symbol.toPrimitive coercion to number or string
Symbol.toStringTag what Object.prototype.toString reports
Symbol.hasInstance instanceof
Symbol.toStringTag2
Symbol.iterator is the one you will implement.
class Deck {
#cards = [1, 2, 3];
*[Symbol.iterator]() { yield* this.#cards; }
}Symbol.iterator
3
[...new Deck()]; // [1, 2, 3]
for (const c of new Deck()) { }4
That is why arrays, strings, Map and Set work with for...of and a plain object does not.
MapSetfor...of
5
Symbol.toPrimitive controls coercion.
class Money {
constructor(a) { this.amount = a; }
[Symbol.toPrimitive](hint) {
return hint === 'number' ? this.amount : `Rs ${this.amount}`;
}
}Symbol.toPrimitiveRs ${this.amount}
6
const m = new Money(100);
+m; // 100
`${m}`; // 'Rs 100'
m + 0; // 100 -- hint is 'default'${m}
7
Three hints: 'number', 'string' and 'default'. Without this, an object coerces via valueOf and
toString, which is the older mechanism.
'number''string''default'valueOftoString
8
Symbol.toStringTag fixes type reporting.
class Money { get [Symbol.toStringTag]() { return 'Money'; } }
Object.prototype.toString.call(new Money()); // '[object Money]'Symbol.toStringTag
9
Symbol.hasInstance customises instanceof.
class Even {
static [Symbol.hasInstance](n) { return n % 2 === 0; }
}
4 instanceof Even; // trueSymbol.hasInstanceinstanceof
10
Clever, and easily confusing — the operator no longer means what readers expect.
11
The general point. These are extension points, not tricks. They are how a user-defined type participates
in language features that would otherwise be reserved for built-ins.
12
In practice you will implement one or two. Symbol.iterator on a custom collection, occasionally
Symbol.asyncIterator on a stream-like object. The rest are worth recognising in library code rather than
reaching for.
Symbol.iteratorSymbol.asyncIterator
13
What the interviewer is probing.1. "What is a well-known symbol?" Probing: the concept. Stalls: "A constant." Moves up: a
built-in symbol the language itself looks for, letting your object hook into syntax —
Symbol.iterator, Symbol.asyncIterator, Symbol.toPrimitive.
14
2. "How do you control how an object converts to a primitive?" Probing: the hook. Stalls:
"toString." Moves up: Symbol.toPrimitive, which receives a hint of number, string or default and
takes precedence over valueOf and toString.
15
3. "What does Symbol.toStringTag change?" Probing: the output. Stalls: "Nothing." *Moves
up:* the result of Object.prototype.toString, so a custom class can report [object MyType].
16
4. "Why are these symbols rather than strings?" Probing: the design. Stalls: "Style." *Moves
up:* a string key could collide with a real property; a symbol cannot.