While ES2015 has introduced many language features that have been on developers’ wish lists for some time, there are some new features that are less well known and understood, and the benefits of which are much less clear — such as symbols.
The symbol is a new primitive type, a unique token that’s guaranteed never to clash with another symbol. In this sense, you could think of symbols as a kind of UUID (universally unique identifier). Let’s look at how symbols work, and what we can do with them.
Key Takeaways
- ES6 introduces a new primitive type, the symbol, which is a unique token that never clashes with another symbol, making it a good replacement for strings or integers as class/module constants.
- Symbols can be used as object property keys, providing unique keys that won’t conflict with existing properties or methods and are not included when the object is serialized.
- Well-known symbols are predefined properties of the Symbol function used to customize the behavior of certain language features, such as iterators, and to implement new functionalities.
- ES6 includes a global symbol registry, allowing the storage and retrieval of symbols across different execution contexts, such as between a document and an embedded iframe or service worker.
Creating New Symbols
Creating new symbols is very straightforward and is simply a case of calling the Symbol function. Note that this is a just a standard function and not an object constructor. Trying to call it with the new
operator will result in a TypeError
. Every time you call the Symbol
function, you’ll get a new and completely unique value.
const foo = Symbol();
const bar = Symbol();
foo === bar
// <-- false
Symbols can also be created with a label, by passing a string as the first argument. The label doesn’t affect the value of the symbol, but is useful for debugging, and is shown if the symbol’s toString()
method is called. It’s possible to create multiple symbols with the same label, but there’s no advantage to doing so and this would probably just lead to confusion.
let foo = Symbol('baz');
let bar = Symbol('baz');
foo === bar
// <-- false
console.log(foo);
// <-- Symbol(baz)
What Can I Do With Symbols?
Symbols could be a good replacement for strings or integers as class/module constants:
class Application {
constructor(mode) {
switch (mode) {
case Application.DEV:
// Set up app for development environment
break;
case Application.PROD:
// Set up app for production environment
break;
case default:
throw new Error('Invalid application mode: ' + mode);
}
}
}
Application.DEV = Symbol('dev');
Application.PROD = Symbol('prod');
// Example use
const app = new Application(Application.DEV);
String and integers are not unique values; values such as the number 2
or the string development
, for example, could also be in use elsewhere in the program for different purposes. Using symbols means we can be more confident about the value being supplied.
Another interesting use of symbols is as object property keys. If you’ve ever used a JavaScript object as a hashmap (an associative array in PHP terms, or dictionary in Python) you’ll be familiar with getting/setting properties using the bracket notation:
const data = [];
data['name'] = 'Ted Mosby';
data['nickname'] = 'Teddy Westside';
data['city'] = 'New York';
Using the bracket notation, we can also use a symbol as a property key. There are a couple of advantages to doing so. First, you can be sure that symbol-based keys will never clash, unlike string keys, which might conflict with keys for existing properties or methods of an object. Second, they won’t be enumerated in for … in
loops, and are ignored by functions such as Object.keys()
, Object.getOwnPropertyNames()
and JSON.stringify()
. This makes them ideal for properties that you don’t want to be included when serializing an object.
const user = {};
const email = Symbol();
user.name = 'Fred';
user.age = 30;
user[email] = 'fred@example.com';
Object.keys(user);
// <-- Array [ "name", "age" ]
Object.getOwnPropertyNames(user);
// <-- Array [ "name", "age" ]
JSON.stringify(user);
// <-- "{"name":"Fred","age":30}"
It’s worth noting, however, that using symbols as keys doesn’t guarantee privacy. There are some new tools provided to allow you to access symbol-based property keys. Object.getOwnPropertySymbols()
returns an array of any symbol-based keys, while Reflect.ownKeys()
will return an array of all keys, including symbols.
Object.getOwnPropertySymbols(user);
// <-- Array [ Symbol() ]
Reflect.ownKeys(user)
// <-- Array [ "name", "age", Symbol() ]
Well-known Symbols
Because symbol-keyed properties are effectively invisible to pre-ES6 code, they’re ideal for adding new functionality to JavaScript’s existing types without breaking backwards compatibility. The so-called “well-known” symbols are predefined properties of the Symbol
function that are used to customize the behavior of certain language features, and are used to implement new functionality such as iterators.
Symbol.iterator
is a well-known symbol that’s used to assign a special method to objects, which allows them to be iterated over:
const band = ['Freddy', 'Brian', 'John', 'Roger'];
const iterator = band[Symbol.iterator]();
iterator.next().value;
// <-- { value: "Freddy", done: false }
iterator.next().value;
// <-- { value: "Brian", done: false }
iterator.next().value;
// <-- { value: "John", done: false }
iterator.next().value;
// <-- { value: "Roger", done: false }
iterator.next().value;
// <-- { value: undefined, done: true }
The built-in types String
, Array
, TypedArray
, Map
and Set
all have a default Symbol.iterator
method which is called when an instance of one of these types is used in a for … of
loop, or with the spread operator. Browsers are also starting to use the Symbol.iterator
key to allow DOM structures such as NodeList
and HTMLCollection
to be iterated over in the same way.
The Global Registry
The specification also defines a runtime-wide symbol registry, which means that you can store and retrieve symbols across different execution contexts, such as between a document and an embedded iframe or service worker.
Symbol.for(key)
retrieves the symbol for a given key from the registry. If a symbol doesn’t exist for the key, a new one is returned. As you might expect, subsequent calls for the same key will return the same symbol.
Symbol.keyFor(symbol)
allows you to retrieve the key for a given symbol. Calling the method with a symbol that doesn’t exist in the registry returns undefined:
const debbie = Symbol.for('user');
const mike = Symbol.for('user');
debbie === mike
// <-- true
Symbol.keyFor(debbie);
// <-- "user"
Use Cases
There are a couple of use cases where using symbols provides an advantage. One, which I touched on earlier in the article, is when you want to add “hidden” properties to objects that won’t be included when the object is serialized.
Library authors could also use symbols to safely augment client objects with properties or methods without having to worry about overwriting existing keys (or having their keys overwritten by other code). For example, widget components (such as date pickers) are often initialized with various options and state that needs to be stored somewhere. Assigning the widget instance to a property of the DOM element object is not ideal, because that property could potentially clash with another key. Using a symbol-based key neatly side steps this issue and ensures that your widget instance won’t be overwritten. See the Mozilla Hacks blog post ES6 in Depth: Symbols for a more detailed exploration of this idea.
Browser Support
If you want to experiment with symbols, mainstream browser support is quite good. As you can see, the current versions of Chrome, Firefox, Microsoft Edge and Opera support the Symbol type natively, along with Android 5.1 and iOS 9 on mobile devices. There are also polyfills available if you need to support older browsers.
Conclusion
Although the primary reason for the introduction of symbols seems to have been to facilitate adding new functionality to the language without breaking existing code, they do have some interesting uses. It’s worthwhile for all developers to have at least a basic knowledge of them, and be familiar with the most commonly used, well-known symbols and their purpose.
Frequently Asked Questions about ES6 Symbols
What are the practical uses of ES6 Symbols?
ES6 Symbols are a new primitive type introduced in ES6 (ECMAScript 6). They are unique and immutable data types that can be used as identifiers for object properties. They can be used to create private properties for objects, to implement meta-level programming concepts, and to avoid naming collisions. They can also be used to define custom iteration behaviors for objects.
How can I create a new Symbol in JavaScript?
You can create a new Symbol by using the Symbol() function. This function will return a unique Symbol each time it is called, even if you pass the same argument to it. For example, let symbol1 = Symbol('symbol'); let symbol2 = Symbol('symbol');
Here, symbol1 and symbol2 are different Symbols, even though the same argument ‘symbol’ was passed to the Symbol() function.
How can I use Symbols as object keys?
You can use Symbols as object keys by using them in square brackets. For example, let symbol = Symbol('key'); let obj = {}; obj[symbol] = 'value';
Here, the Symbol ‘symbol’ is used as a key in the object ‘obj’.
What is the global symbol registry in JavaScript?
The global symbol registry is a shared environment where you can create, access, and share Symbols across different realms (like iframes or service workers). You can create a Symbol in the global registry with Symbol.for(key)
, and access it with Symbol.keyFor(symbol)
.
How can I use Symbols with objects?
You can use Symbols with objects to create unique keys. This can be useful to avoid naming collisions, as Symbols are always unique. For example, let symbol = Symbol('key'); let obj = { [symbol]: 'value' };
Here, the Symbol ‘symbol’ is used as a key in the object ‘obj’.
Can I convert a Symbol to a string?
Yes, you can convert a Symbol to a string by calling the toString()
method on it. This will return a string in the format ‘Symbol(description)’, where ‘description’ is the optional description you passed to the Symbol() function.
Can I use Symbols with arrays?
Yes, you can use Symbols with arrays. For example, you can use a Symbol as a unique property key to store metadata about the array. However, Symbols are not included in the array’s length property, and they are not returned by most array methods.
Can I use Symbols with functions?
Yes, you can use Symbols with functions. For example, you can use a Symbol as a unique property key to store metadata about the function. However, Symbols are not included in the function’s length property, and they are not returned by most function methods.
Can I use Symbols with classes?
Yes, you can use Symbols with classes. For example, you can use a Symbol as a unique property key to store metadata about the class. However, Symbols are not included in the class’s length property, and they are not returned by most class methods.
Can I use Symbols with strings?
Yes, you can use Symbols with strings. For example, you can use a Symbol as a unique property key to store metadata about the string. However, Symbols are not included in the string’s length property, and they are not returned by most string methods.
Nilson is a full-stack web developer who has been working with computers and the web for over a decade. A former hardware technician, and network administrator. Nilson is now currently co-founder and developer of a company developing web applications for the construction industry. You can also find Nilson on the SitePoint Forums as a mentor.