JavaScript Symbols
Use unique Symbol values as collision-free object keys and customize built-in object behavior.
TL;DR
- 01Instantiate unique primitive values with the built-in
Symbolfactory. - 02Configure unique collision-free keys hidden from standard object loop enumerations.
- 03Use well-known symbols to customize core language behaviors like iteration.
Tips
- 01Use local symbols to declare properties that will never collide with third-party keys.
- 02Retrieve shared symbol values across realms using the global
Symbol.for()registry.
Warnings
- 01Avoid calling
Symbolusingnewbecause symbols are primitives rather than constructible classes. - 02Remember that symbol properties are omitted by standard operations like
JSON.stringifyand loops.
What Symbols Are
Unique primitiveGenerates unique primitive values on every function call.
const a = Symbol("id");
const b = Symbol("id");
console.log(a === b); // falseDescription debuggingAssigns debugging descriptions which do not affect symbol identity.
const s = Symbol("label");
console.log(s.description); // "label"Primitives typeReturns symbol from typeof checks.
console.log(typeof Symbol()); // "symbol"Object Keys
Symbol propertiesAssigns properties using symbol keys to guarantee collision-free attributes.
const KEY = Symbol("key");
const user = { name: "Ada", [KEY]: 42 };Bracket accessesQueries symbol keys using bracket notation rather than dot accesses.
console.log(user[KEY]); // 42Descriptors listingLists symbol keys using Reflect methods.
Reflect.ownKeys(user); // ["name", Symbol(key)]Omitted Enumeration
Loop exclusionExcludes symbol properties from keys listings and loops.
const obj = { name: "A", [Symbol("id")]: 1 };
console.log(Object.keys(obj)); // ["name"]JSON serializationOmits symbol keys during stringify operations.
console.log(JSON.stringify(obj)); // '{"name":"A"}'Explicit queriesQueries symbols using specialized getOwnPropertySymbols calls.
Object.getOwnPropertySymbols(obj); // [Symbol(id)]Well-Known Symbols
Symbol.iteratorEnables custom iteration behavior on plain objects.
const range = {
[Symbol.iterator]() {
return { next: () => ({ done: true }) };
}
};
[...range];Symbol.toPrimitiveEnforces custom conversions into primitives.
const cash = {
amount: 50,
[Symbol.toPrimitive](hint) {
return hint === "string" ? `$${this.amount}` : this.amount;
}
};Symbol.hasInstanceOverrides instanceof check mechanics for target configurations.
class Even {
static [Symbol.hasInstance](num) {
return num % 2 === 0;
}
}
console.log(4 instanceof Even); // trueGlobal Registry
Symbol.forRegisters shared symbol instances in a global scope index.
const a = Symbol.for("app.id");
const b = Symbol.for("app.id");
console.log(a === b); // trueSymbol.keyForReturns registered index strings matching active shared symbols.
console.log(Symbol.keyFor(a)); // "app.id"In Practice
Uses local Symbols to attach internal metadata to objects safely, preventing collision with user properties.
- 01Instantiate a local Symbol identifier to act as the metadata key.
- 02Define a setMetadata function mapping data to the object symbol key.
- 03Assign metadata to the object using bracket notation entries.
- 04Define a getMetadata function to retrieve values using the key.
- 05Return the helper functions while keeping the Symbol hidden.
function createMetadataSystem() {
const METADATA = Symbol("internal_metadata");
function setMetadata(obj, data) {
obj[METADATA] = data;
}
function getMetadata(obj) {
return obj[METADATA];
}
return { setMetadata, getMetadata };
}FAQ
A Symbol represents a unique primitive value that avoids key collisions. It allows adding custom properties to objects safely. Well-known symbols hook directly into core operations.
By design, serialization routines like JSON.stringify only process string-keyed properties. This restriction lets symbols act as hidden metadata boundaries. They remain safe from accidental log outputs.
The Symbol() factory guarantees unique instances per call. The Symbol.for() method queries the global symbol registry first. It retrieves matching instances if they already exist.
Well-known symbols are built-in hooks like Symbol.iterator. They enable objects to customize default language behaviors. This includes customizing string casting and enabling iteration protocols.
No, they are not. The Object.getOwnPropertySymbols() method returns all symbol keys from target instances. Anyone holding reference handles can access the property values.