Object property keys may be string or symbol type A “symbol” represents a unique identifier Symbols are guaranteed to be unique Even if we create many symbols with the same description, they are different values Create
let id1 = Symbol("id")
let id2 = Symbol("id")
id1 == id2 // false
typeof id1 // "symbol"
alert(id1) // TypeError: Cannot convert a Symbol value to a string
alert(id1.toString()); // "Symbol(id)"
Description property
let id1 = Symbol("id")
id1.description // "id"
Symbol in object literal
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // not "id": 123 // That’s because we need the value from the variable id as the key, not the string “id”
};
“Hidden” properties we can access the data using the symbol as the key nobody can overwrite it, coz nobody can generate same id, symbol cannot be accessed accidentally
let user = { name: "John" };
let id = Symbol("id");
user[id] = 1;
user[id] // 1
Symbols are skipped by for…in loop
for (let key in user ) {
console.log(key, user [key]); // name John
}
// Symbol(id): 1 is skipped
Object.keys(user) // ["name"] // Symbol is not shown
Copy object with Symbol
let id = Symbol("id");
let user = { [id]: 123 };
let clone = Object.assign({}, user);
clone[id]; // 123
Global symbols We can create symbols global registry & access them later It guarantees that repeated accesses by the same name return exactly the same symbol. Symbol.for(key) checks the global registry, and creates or returns existing symbol Symbols inside the registry are called global symbols They are accessible everywhere in the code – that’s what they are for. There are many system symbols used by JavaScript which are accessible as Symbol.* We can use them to alter some built-in behaviors
// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created
// read it again (maybe from another part of the code)
let idAgain = Symbol.for("id");
id === idAgain // true // the same symbol
// Symbol.for works only for global symbols
let globalSymbol = Symbol.for("name");
let localSymbol = Symbol("name");
Symbol.keyFor(globalSymbol) // name, global symbol
Symbol.keyFor(localSymbol) // undefined, not global
globalSymbol.description // name
localSymbol.description // name
// There are many system symbols used by JavaScript which are accessible as Symbol.*.
// We can use them to alter some built-in behaviors.
Symbol.hasInstance
Symbol.isConcatSpreadable
Symbol.iterator
Symbol.toPrimitive
// …and so on.
Example to understand symbols
let lib = { name: "ABC" };
lib["id"] = 5;
lib["id"] = 6; // The value is changed because it is String [KEY]!!
lib[Symbol("id")] = 123;
lib[Symbol("id")] = 124; //Not changed
lib // { name: "ABC", id: 6, Symbol(id): 123, Symbol(id): 124 }
Object to primitive conversion For data conversion into a primitive value JavaScript tries to find and call three object methods: Call obj[Symbol.toPrimitive](hint) – the method with the symbolic key Symbol.toPrimitive . Otherwise if hint is string JS tries obj.toString() or obj.valueOf() . Otherwise if hint is number or "default" JS tries obj.valueOf() or obj.toString() . Knowing that we can make our data be convertible into primitive value by JS natively. Basic object is not converted into number or boolean.
let obj = {a: "5"}
Boolean({obj}) // true // All objects are true in a boolean context
obj.toString() // "[object Object]"
+obj // NaN
But when we add symbol it can do conversion.
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
return hint == "string" ? this.name : this.money;
}
};
alert(user); // "John"
alert(+user); // 1000
alert(user + 500); // 1500
In the absence of Symbol.toPrimitive conversion can be handled by valueOf() , toString() .
let user = {
name: "John",
money: 1000,
// for hint="string"
toString() {
return this.name;
},
// for hint="number" or "default"
valueOf() {
return this.money;
}
};
alert(user); // "John"
alert(+user); // 1000
alert(user + 500); // 1500
Make iterable contactable
let arr = [1, 2]
let arrayLike = {
0: "something",
length: 1,
}
arr.concat(arrayLike) // [1,2,[object]]
let arr = [1, 2];
let arrayLike = {
0: "something",
1: "else",
[Symbol.isConcatSpreadable]: true,
length: 2,
};
arr.concat(arrayLike) //[1, 2, "something", "else"]
Make object iterable To make an iterable we need to add Symbol.iterator & next() method.
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) alert(num); // 1, then 2, 3, 4, 5