A Symbol is a primitive value that is guaranteed to be unique. No two Symbols are ever equal, even if they have the same description. Their main job is to be object keys that can never clash with any other key, and they are how JavaScript itself adds hidden hooks like Symbol.iterator.
const a = Symbol("id");
const b = Symbol("id");
console.log(a === b); // false every Symbol is unique
console.log(typeof a); // "symbol"
console.log(a.description); // "id" the label is just for humans
new Symbol() throws a TypeError. Symbols are primitives, so you call Symbol() directly, like String() or Number().
The description is optional. Without one, description is undefined, and every Symbol() is still unique:
const x = Symbol();
const y = Symbol();
console.log(x === y, x.description, String(x));
Use a Symbol inside square brackets to create a property no other code can accidentally overwrite:
const ID = Symbol("id");
const user = {
name: "Ava",
id: "from-server", // a normal string key
[ID]: 1042 // a separate, symbol key
};
console.log(user.id); // "from-server"
console.log(user[ID]); // 1042 no collision
This matters when you attach your own data to objects that other code also uses. A string key like "id" might already exist; your Symbol key cannot clash with anything.
Object.keys(user) // ["name", "id"]
JSON.stringify(user) // '{"name":"Ava","id":"from-server"}'
for (const k in user) { } // visits name and id only
Object.getOwnPropertySymbols(user) // [Symbol(id)] you have to ask specifically
getOwnPropertySymbols can see them. For true privacy, use closures, WeakMaps, or #private class fields.
Sometimes you want the same Symbol in different files. Symbol.for(key) looks it up in a global registry, creating it only the first time:
Symbol.for("app.theme") === Symbol.for("app.theme") // true
Symbol("app.theme") === Symbol("app.theme") // false
JavaScript has built-in Symbols that let your objects plug into language features. You already used one:
| Symbol.iterator | Makes an object work with for...of and spread (JS Iterators) |
| Symbol.toPrimitive | Controls how an object turns into a number or string |
| Symbol.toStringTag | Sets the name shown by Object.prototype.toString |
| Symbol.asyncIterator | Like Symbol.iterator, for data that arrives over time (for await...of; not covered in this course) |
const money = {
cents: 1999,
[Symbol.toPrimitive](hint) {
return hint === "number" ? this.cents / 100 : `$${(this.cents / 100).toFixed(2)}`;
}
};
console.log(`Total: ${money}`); // "Total: $19.99"
console.log(+money * 2); // 39.98
JS Iterators showed that for...of works on anything with a [Symbol.iterator] method. Here is W3Schools' version: a plain object that loops over its own data:
const myObject = {
data: ["A", "B", "C"],
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
const value = data[index];
index++;
return { value: value, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const x of myObject) console.log(x);
Symbol.toStringTag changes the name that Object.prototype.toString reports (the kind trick from Object Types):
const box = { [Symbol.toStringTag]: "Box" };
console.log(String(box));
const s = Symbol("tag");
"Key: " + s // TypeError: Cannot convert a Symbol value to a string
"Key: " + String(s) // "Key: Symbol(tag)"
s.description // "tag"
`Key: ${s}` is the same error as "Key: " + s: TypeError: Cannot convert a Symbol value to a string. Inside a template, use String(s) or s.description. (console.log(s) is fine: it shows Symbol(tag).)
Symbol.iterator. Reach for your own when you add properties to objects you do not control and need a guarantee they will never clash.
What does this log?
console.log(Symbol("a") === Symbol("a"), Symbol.for("a") === Symbol.for("a"));
false true. Every Symbol() call makes a new, unique symbol. Symbol.for looks the key up in the global registry, so both calls return the same symbol.const id = Symbol("id"); and const user = { name: "Ava", [id]: 7 }; What is Object.keys(user)?
["name", "id"]["name"]["name", Symbol(id)][]Object.keys, for...in, and JSON.stringify. Use Object.getOwnPropertySymbols to find them.Which is correct, new Symbol("x") or Symbol("x")? Why?
Symbol("x"). Symbols are primitives, so there is no wrapper to construct: new Symbol("x") throws TypeError: Symbol is not a constructor.Object.keys and JSON.stringifyObject.getOwnPropertySymbolsSymbol.toPrimitive method so `${obj}` prints something friendly