Everything you need to know about JavaScript symbols

Narek Ghevandiani
The Startup
Published in
9 min readMar 25, 2020

--

A JavaScript Symbol is a relatively new JavaScript “feature”. It was introduced back in 2015 as part of ES6. In this article, I am going to cover:

  1. What exactly is a JavaScript Symbol
  2. The motivation of adding the data type to the language
  3. Whether the data type was successful in solving the problem it was supposed to
  4. The differences with the symbol data type in other languages, for example Ruby
  5. Well-known JavaScript symbols
  6. Benefiting from the data type

JavaScript symbol

Symbols were added to the lineup of primitive data types in JavaScript in 2015. It was part of ES6 specification and its sole purpose is to act as a unique identifier of object properties, i.e. it can be used as a key in objects. You can think of symbols as big numbers and every time you create a symbol, a new random number gets generated (uuid). You can use that symbol (the random big number) as a key in objects.

A symbol is created by calling the Symbol function which takes an optional argument string which is used only for debugging purposes and acts as a description of the symbol. The Symbol function returns a unique symbol value.

Note that a Symbol is not a constructor and cannot be called with new.

It is also possible to create symbols which will be assigned to the global symbol registry. The methods Symbol.for() and Symbol.keyFor() help create and read Symbols in the global symbol registry. The Symbol.for() method looks in the global symbol registry and retrieves or initializes the symbol depending if it is found or not.

And the Symbol.keyFor() method looks for the symbol in the global symbol registry and returns its key if found and undefined otherwise. Think of the global symbol registry as a global object where the keys are the strings passed to Symbol.for() and the values are the symbols.

Symbols registered to the global symbol registry are not only accessible in all scopes, but even accessible across realms.

Motivations of adding the new data type

One of the reasons of adding the data type was to enable private properties in JavaScript. Before symbols, privacy or immutability were being solved with closures, proxies, and other workarounds. But all of the solutions are too verbose and require a lot of code and logic to achieve their purpose.

So let’s see how the Symbol was supposed to solve the issue. Every symbol value returned from the Symbol function is unique and can be used as an object property identifier. That is the main purpose of the Symbol.

Since every symbol is unique, and in no way can two symbols be equal to each other, if a symbol is used as a property identifier and is not available in a scope, that property cannot be accessed from that scope.

Symbols defined in the global symbol registry can be accessed with Symbol.for() and will be the same.

Okay, so symbols are cool. They help us make unique values that can never be repeated and use them to hide properties. But do they really solve the privacy problem?

Do symbols achieve property privacy?

JavaScript symbol does NOT achieve property privacy. You cannot rely on symbols to hide something from the user of your library. There is a method defined on the Object class called Object.getOwnPropertySymbols() that takes an object as an argument and returns an array of property symbols of the argument object.

Additionally, if the symbol is assigned to the global symbol registry, nothing can stop accessing the symbol and its property value.

Symbol in computer programming

If you are familiar with other programming languages, you will know that they have symbols too. And in fact, even if the name of the datatype is the same, there are pretty significant differences between them.

Now let’s talk about symbols in programming in general. The definition of a symbol in Wikipedia is the following:

A symbol in computer programming is a primitive data type whose instances have a unique human-readable form.

In JavaScript symbol is a primitive datatype and although the language does not force you to make the instance human-readable, you can provide the symbol with a debugging description property.

Given that, we should know that there are some differences between JS symbols and symbols in other languages. Let’s take a look at Ruby symbols. In Ruby, Symbol objects are usually used to represent some strings. They are generated using the colon syntax and also by type conversion using the to_sym method.

If you noticed, we never assign the “created” symbol to a variable. If we use (generate) a symbol in the Ruby program it will always be the same during the entire execution of the program, regardless of its creation context.

In JavaScript, we can replicate this behavior by creating a symbol in the global symbol registry.

A major difference between symbol in the 2 languages is that in Ruby, symbols can be used instead of string, and in fact in many cases they auto-convert to strings. Methods available on string objects are also available on symbols and as we saw string can be converted to symbols using the to_sym method.

We already saw the reasons and the motivation of adding symbols to JavaScript, now let’s see what is their purpose in Ruby. In Ruby, we can think of symbols as immutable strings, and that alone results in many advantages of using them. They can be used as object property identifiers, and usually are.

Symbols also have performance advantages over strings. Every time you use the string notation, a new object gets created in the memory while symbols are always the same.

Now imagine we use a string as a property identifier and create 100 of that object. Ruby will have to also create 100 different string objects. That can be avoided by using symbols.

Another use-case of symbols is showing status. For example, it is a good practice for functions to return a symbol, indicating the success status like (:ok, :error) and the result.

In Rails (a famous Ruby web-app framework), almost all HTTP status codes can be used with symbols. You can send status :ok, :internal_server_error or :not_found, and the framework will replace them with correct status code and message.

To conclude, we can say that symbols are not the same and do not share the same purpose in all programming languages and as a person who already was familiar with Ruby symbols, for me JavaScript symbols and their motivation were a bit confusing.

Note: In some programming languages (erlang, elixir), symbol is called an atom.

Well-known JavaScript symbols

JavaScript has some built-in symbols which allow the developer to access some properties which were not exposed before the introduction of symbols to the language.

Here are some of the well-known JavaScript symbols that are used for iteration, Regexp, etc.

Symbol.iterator

This symbol gives the developer access to the default iterator for an object. It is used in for…of and its value should be a generator function.

function*() {} is the syntax for defining a generator function. A generator function returns a Generator object.

yield is a keyword used to pause and resume generator functions.

See more on generator functions and yield.

For async iteration there is Symbol.asyncIterator which is used by for await…of loop.

Symbol.match

As we know, functions like String.prototype.startsWith(), String.prototype.endsWith() take a string as their first argument.

Let’s try passing a regexp instead of a string to the function, we get a type error.

Actually, what happens is that the functions check specifically whether the passed argument is a regexp or not. We can say that an object is not intended to be used as a regexp by setting its Symbol.match property to false or to another falsey value.

Note: Honestly, I am not sure why you would want to do this. The code above is an example to demonstrate how to use the Symbol.match . It seems like a hack and it will be problematic if used like this because it changes the behavior of functions that are used a lot. I cannot think of any real use-cases for using this one.

Benefiting from JavaScript symbols

Although JS Symbol is not being used widely and did not solve the property privacy problem, we still can benefit from it.

We can use the Symbol to define some metadata on the object. For example, we want to create a dictionary which we will implement by adding word and definition pairs to the object and for some computational reasons, we want to keep track of the word count in the dictionary. The word count in this case can be considered metadata. It is not really a valuable piece of information for the user and the user may not want to see it when say iterating over the object.

We can solve this problem by keeping the word count property keyed with a symbol. In this case, we avoid the problem of the user accidentally accessing it.

And the reason for which symbols will be used the most is probably property name collisions. Sometimes we get and set object properties when iterating over them, or we use a dynamic value to access the property (with using the obj[key] notation) and as a result of that, accidentally mutate the property we never wanted to. So we can solve this problem by using symbols as property identifiers. In this case, we can never land on that key while iterating over the object or using a dynamic value. Iteration case will not happen because we can never land on them while iterating with for…in

The dynamic valued key case cannot happen because there is no other value equal to a symbol except that symbol.

And of course, well-known symbols like Symbol.iterator or Symbol.asyncIterator can have interesting use-cases.

I covered the important concepts and practices needed for grasping the idea of JavaScript Symbol. Of course there is a lot more to cover like other well-known symbols, or use cases of crossing realms with symbols, but I will leave out some useful material that cover those and other parts of JavaScript Symbol.

--

--