Introduction
1. What exactly is a Symbol in Javascript ?
It's a primitive data of Javascript, along with String, Number (Integers, Floats, Infinity, NaN), Boolean, null, and undefined. And it represents a "unique" Identifier. Introduced in ECMAScript 2015, ES6.
2. How symbols
can be used ?
They bring a few benefits to the language and are particularly useful when used as object properties. But, what can they do for us that strings cannot? Usually, we use strings as object properties.
Bird's Eye View
- Once you create a symbol, its value is kept private and for internal use.
- You create a symbol by calling the
Symbol()
global factory function. - Every time you invoke
Symbol()
we get a new and unique symbol, guaranteed to be different from all other symbols
Symbol() === Symbol() //false
- Symbols are used to identify Object Properties.
- And to avoid name clashing btw properties, since no symbol is equal to another.
- Symbols are not enumerated, which means that they do not get included in a for..of or for..in loop ran upon an object.
const NAME = Symbol()
const person = {
[NAME]: 'Jonathan'
}
person[NAME] //'Sumanth'
const RUN = Symbol()
person[RUN] = () => 'Person is running'
console.log(person[RUN]()) //'Person is running'
Creating Symbols
A value of this type can be created using Symbol()
:
let id = Symbol();
// here id is the new symbol
consolo.log(id); // Symbol()
Since a symbol
is a primitive value, if you attempt to create a symbol using the new operator, you will get an error:
let s = new Symbol(); // TypeError
let arr = new Array(); // Totally acceptable
We can also give symbol a description in string format as a first argument while instantiating a Symbol and it is optional and it doesn't affect the symbol itself.
This description is mostly useful for debugging purpose.
let id = Symbol(); // without description
let id2 = Symbol("firstname"); // symbol with desc "firstname"
console.log(id2); // Symbol("firstname")
console.log(id2.description); // "firstname"
The Symbol()
function creates a new unique value each time you call it:
console.log(Symbol() === Symbol()); // false
const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2); // false
Even if we give the same description to different symbols they are totally different and that's why Symbol is a unique Identifier.
let id1 = Symbol("desc");
const str = 'desc';
let id2 = Symbol("desc");
console.log(id1 == id2); // false
console.log(s1 === str); // false
let value1 = "Sumanth"; // primitive type string
let value2 = "Sumanth";
console.log(value1===value2); // true
Why Symbols are Introduced
- Basically, primitive values are Immutable. They can't be changed. Of course, a variable with a primitive assigned can be reassigned. For example, when you write the code let x = 1; x++; you've reassigned the variable x. But, you haven't mutated the primitive numeric value of 1.
- However, if you modify a non-primitive value, the modified value will also be modified where it has been called from.
function primitiveMutator(val) {
return val = val + 1;
}
let x = 1;
primitiveMutator(x); // 2
console.log(x); // 1 (value not changed)
function objectMutator(obj) {
obj.prop = obj.prop + 1;
}
let obj = { prop: 1 };
objectMutator(obj);
console.log(obj.prop); // 2
- Primitive values are equal to another primitive with an equivalent value.
const first = "abc" + "def";
const second = "ab" + "cd" + "ef";
console.log(first === second); // true
- But, equivalent non-primitive values will not result in values which are exactly equal.
const obj1 = { name: "Intrinsic" };
const obj2 = { name: "Intrinsic" };
console.log(obj1 === obj2); // false
// Though, their .name properties ARE primitives:
console.log(obj1.name === obj2.name); // true
Objects play an elemental role in the JavaScript language. They’re often used as collections of key/value pairs. However, this is a big limitation of using them in this manner: Until symbols existed, object keys could only be strings. If we ever attempt to use a non-string value as a key for an object, the value will be coerced to a string.
NOTE: Map
data structure was created in part to allow for key/value storage in situations where a key is not a string. Whereas in Objects we can use only strings as keys
Where can we use Symbols
(Symbols actual Use-case)
- There are the ways to add new
properties
to an Object:
let obj = {name:"sumanth"};
obj["Native"] = "Mars"; // using square brackets
console.log(obj["Native"]); // "Mars"
obj.phno = 96584; // using dot operator
console.log(obj.phno) // 96584
console.log(obj); // {name: "sumanth", Native: "Mars", phno: 96584}
- And the another way using Symbols as object Properties:
let obj = {}; // new object
let sym = Symbol("name"); // new symbol
obj[sym] = 'foo'; // adding new property to obj
obj = {
[sym]:"foobar", // another way of using symbols // not as "sym": 123
[Symbol('name')]: 'foo' // directly creating symbols here
}
What’s the benefit of using Symbol("name") as property name or key over a string "name"?
- Properties with a symbol key don't appear in the JSON representation of your object. Not even the for-in loop or Object.keys can enumerate them:
let id= Symbol('name'); // new symbol
var newObj = {
[id]:369, // symbol property
okay:"A text"
}
JSON.stringify(newObj) // {"okay":"A text"}
for( var prop in newObj) {
console.log(newObj[prop]); // A text
}
console.log(Object.keys( newObj)); // ['okay']
If you can observe in the above examples symbol properties are not appeared when we try to access them. In this way they allow us to create some "HIDDEN" properties of an object.
But these properties are not fully private in a strict sense. We can access them using Object.getOwnPropertySymbols()
Object.getOwnPropertySymbols(newObj)[0] // Symbol(name)
newObj[Object.getOwnPropertySymbols(newObj)[0]]; // 369
- Let's say we imported one user
object
from another code
let user = { name: "John" }; // imported code
// Our script wants to create "id" property or identifier
user.id = "Our id value";
// ...Another script also wants "id" for its purposes...
user.id = "Their id value"
// Boom! overwritten by another script!
So, to overcome this conflict we can use Symbols as property names.
let user = { // belongs to another code
name: "John"
};
let id = Symbol("id");
user[id] = 1;
Also, imagine that another script wants to have its own identifier inside user
, for its own purposes. That may be another JavaScript library, so that the scripts are completely unaware of each other.
Then that script can create its own Symbol("id")
, like this:
// ...
let id = Symbol("id");
user[id] = "Their id value";
There will be no conflict between our and their identifiers, because symbols are always different, even if they have the same name.
…But if we used a string "id"
instead of a symbol for the same purpose, then there would be a conflict as we seen above.
Summary
To avoid conflicts in identifier names we can use Symbols. If one imported object has a property as "name" and if we accidentally create our property with same "name" then the previous property will be overridden.
Top comments (1)
I understand the differences between symbols and string based keys on object but somehow I am yet not clear why symbols exist.
In terms of non-enumerable properties we can have those with regular objects too. Like we have the length property with any array.
If we talk about sharing objects between libraries, I guess its a not a good idea where different libraries are trying to add properties with same key. If that is happening, probably we are doing something wrong somewhere.
It would be great if you could put out some real world examples where symbols are practically used.