Symbols in JS, and why

ref –

  • https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/
  • https://flaviocopes.com/javascript-symbols/
  • https://medium.com/intrinsic/javascript-symbols-but-why-6b02768f4a5c
  • https://javascript.info/symbol

Basics info

A symbol represents a unique identifier.
A value of this type can be created using Symbol():

Upon creation, we can give the symbol a description (also called a symbol name) , mostly useful for debugging purposes:

Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn’t affect anything.

For instance, here are two symbols with the same description – they are not equal:

…But if we used a string “id” instead of a symbol for the same purpose, then there would be a conflict:

“Hidden” properties

Symbols allow us to create hidden properties of an object, that no other part of code can accidentally access or overwrite.

For instance, if we’re working with user objects, that belong to third-party code. We’d like to add identifiers to them.

Let’s use a symbol key for it:

What’s the benefit of using Symbol(“id”) over a string “id”?

As user objects belong to another code, and that code also works with them, we shouldn’t just add any fields to it. That’s unsafe. Notice the public use of string “name” to change user object value.

But a symbol cannot be accessed accidentally, the third-party code (hidden away in some module) cannot be accessed by us.

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:

Now, there will be no conflict between the original and other id values, 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:

Symbols in a literal

If we want to use a symbol in an object literal {…}, we need square brackets around it.

Like this:

That’s because we need the value from the variable id as the key, not the string “id”.

Symbols are skipped by for…in

For…in iterates through all the indexes of an object/array.
However, symbolic properties do not participate in for..in loop.

For instance:

Object.keys(user) also ignores them.

That’s a part of the general “hiding symbolic properties” principle.

If another script or a library loops over our object, it won’t unexpectedly access a symbolic property.

In contrast, Object.assign copies both string and symbol properties:

There’s no paradox here. That’s by design. The idea is that when we clone an object or merge objects, we usually want all properties to be copied (including symbols like id).

Other Stuff

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. We can see this feature here:

A symbol is a primitive which cannot be recreated

They are useful in situations where disparate libraries want to add properties to objects without the risk of having name collisions.

For example:

By making use of symbols, each library can generate their required symbols upon instantiation. Then the symbols can be checked on objects, and set to objects, whenever an object is encountered.

For this reason, it would seem that symbols do benefit JavaScript.
However, you may be wondering, why can’t each library simply generate a random string, or use a specially namespaced string, upon instantiation?

Well, you’d be right. This approach is actually pretty similar to the approach with symbols. Unless two libraries would choose to use the same property name, then there wouldn’t be a risk of overlap.

Object.keys() does not return symbols

Here is an example of using a symbol as a key within an object:

Notice how they are not returned in the result of Object.keys(). This is, again, for the purpose of backward compatibility. Old code isn’t aware of symbols and so this result shouldn’t be returned from the ancient Object.keys() method.

In addition to that, Symbols do not show up on an Object using for in, for of or Object.getOwnPropertyNames – the only way to get the Symbols within an Object is Object.getOwnPropertySymbols:

This means Symbols give a whole new sense of purpose to Objects – they provide a kind of hidden under layer to Objects – not iterable over, not fetched using the already existing Reflection tools and guaranteed not to conflict with other properties in the object!

Symbols are completely unique…

By default, each new Symbol has a completely unique value. If you create a symbol (var mysym = Symbol()) it creates a completely new value inside the JavaScript engine. If you don’t have the reference for the Symbol, you just can’t use it.
This also means two symbols will never equal the same value, even if they have the same description.

Well, there’s a small caveat to that – as there is also another way to make Symbols that can be easily fetched and re-used: Symbol.for(). This method creates a Symbol in a “global Symbol registry”. Small aside: this registry is also cross-realm, meaning a Symbol from an iframe or service worker will be the same as one generated from your existing frame:

Cross Realm

Problem:

main usage

Macros
As a unique value where you’d probably normally use a String or Integer:
Let’s assume you have a logging library, which includes multiple log levels such as logger.levels.DEBUG, logger.levels.INFO, logger.levels.WARN and so on. In ES5 code you’d like make these Strings (so logger.levels.DEBUG === ‘debug’), or numbers (logger.levels.DEBUG === 10). Both of these aren’t ideal as those values aren’t unique values, but Symbols are! So logger.levels simply becomes:

A place to put metadata values in an Object

You could also use them to store custom metadata properties that are secondary to the actual Object. Think of this as an extra layer of non-enumerability (after all, non-enumerable keys still come up in Object.getOwnProperties). Let’s take our trusty Collection class and add a size reference, which is hidden behind the scenes as a Symbol (just remember that Symbols are not private – and you can – and should – only use them in for stuff you don’t mind being altered by the rest of the app):