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.

How they 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.

  • 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]; //'Jonathan'

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 (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"?

  1. 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
  1. 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 have 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.