A Symbol is a primitive whose only job is to be a unique property key. The spec: "Each Symbol is unique and immutable", and the Symbol type is the set of non-String values that can be used as property keys. Symbol('id') === Symbol('id') is false: the description is just a label, held in an immutable [[Description]] slot and readable via symbol.description.
What that buys you:
- Collision-free keys. A library can add a property to an object it doesn't own without risking a clash with existing string keys or another library's symbols.
- Hidden from the usual enumeration. Symbol-keyed properties don't appear in
Object.keys,for…inorJSON.stringify. They're found withObject.getOwnPropertySymbolsorReflect.ownKeys. This is not privacy: anyone with the object can list them. Use#privatefields for real encapsulation. - Language protocols. Well-known symbols such as
Symbol.iterator,Symbol.hasInstance,Symbol.toPrimitiveandSymbol.toStringTagare keys the engine looks up to customisefor…of,instanceof, type conversion andObject.prototype.toString.
Global registry. Symbol(desc) is always a new symbol. Symbol.for(key) looks up key in a registry shared across the agent's realms and returns the existing symbol, creating one if none exists; Symbol.keyFor(symbol) goes the other way and returns undefined for a symbol that isn't registered. Use it only when separate pieces of code genuinely need the same symbol.
Rules. Call Symbol() without new; new Symbol() throws a TypeError. A symbol won't implicitly convert to a string (`${sym}` throws); use String(sym) or sym.description.