Wednesday, 21 December, 2016 UTC


Summary

This article is a section from the course ES6 in Practice. I created this course during the last couple of months, because there is an evident need for a resource that helps JavaScript developers put theory into practice.
This course won’t waste your time with long hours of video, or long pages of theory that you will never use in practice. We will instead focus on learning just enough theory to solve some exercises. Once you are done with the exercises, you can check the reference solutions to deepen your understanding. In fact, you will sometimes get a chance to discover some theoretical concepts by solving an exercise, then reading about the concept in the reference solution.
If you like this article, check out the course here.
ES6 introduces a new primitive type for JavaScript: Symbols. A JavaScript symbol is created by the global Symbol() function. Each time the Symbol() function is called, a new unique symbol is returned.
let symbol1 = Symbol();
let symbol2 = Symbol();

console.log( symbol1 === symbol2 ); 
> false
Symbols don’t have a literal value. All you should know about the value of a symbol is that each symbol is treated as a unique value. In other words, no two symbols are equal.
Symbol is a new type in JavaScript.
console.log( typeof symbol1 );
> "symbol"
Symbols are useful, because they act as unique object keys.
let myObject = { 
    publicProperty: 'Value of myObject[ "publicProperty" ]'
};

myObject[ symbol1 ] = 'Value of myObject[ symbol1 ]';
myObject[ symbol2 ] = 'value of myObject[ symbol2 ]';

console.log( myObject );
> Object
>    publicProperty: "Value of myObject[ "publicProperty" ]"
>    Symbol(): "Value of myObject[ symbol1 ]"
>    Symbol(): "value of myObject[ symbol2 ]"
>    __proto__: Object

console.log( myObject[ symbol1 ] );
> Value of myObject[ symbol1 ]
When console logging myObject, you can see that both symbol properties are stored in the object. The literal "Symbol()" is the return value of the toString() method called on the symbol. This value denotes the presence of a symbol key in the console. We can retrieve the corresponding values if we have access to the right symbol.
Properties with a symbol key are treated as private. They are missing from the JSON representation of your object. Not even the for-in loop or Object.keys can enumerate them:
JSON.stringify( myObject )
> "{"publicProperty":"Value of myObject[ \"publicProperty\" ] "}"

for( var prop in myObject ) {
    console.log( prop, myObject[prop] );
}
> publicProperty Value of myObject[ "publicProperty" ] 

console.log( Object.keys( myObject ) );
> ["publicProperty"]
Object.getOwnPropertySymbols provides a way to retrieve the symbol keys of your objects:
Object.getOwnPropertySymbols(myObject)
> [Symbol(), Symbol()]

myObject[ Object.getOwnPropertySymbols(myObject)[0] ]
> "Value of myObject[ symbol1 ]"
Never use Object.getOwnPropertySymbols in your code. Treat your symbol keys as private. The only use cases for Object.getOwnPropertySymbols are testing and debugging.
As long as you respect the above rule, your object keys will be private from the perspective of developing your code. In practice however, be aware that others will be able to access your private values. Therefore, symbols are only good for encouraging better development practices.
Even though symbol keys are not enumerable, they still make it to shallow copies of our objects:
clonedObject = Object.assign( {}, myObject );

console.log( clonedObject );
> Object
>    publicProperty: "Value of myObject[ "publicProperty" ]"
>    Symbol(): "Value of myObject[ symbol1 ]"
>    Symbol(): "value of myObject[ symbol2 ]"
>    __proto__: Object
Naming your symbols properly is essential in indicating what your symbol is used for. If you need additional semantic guidance, it is also possible to attach a description to your symbol. The description of the symbol appears in the string value of the symbol.
let leftNode = Symbol( 'Binary tree node' );
let rightNode = Symbol( 'Binary tree node' );

console.log( leftNode )
> Symbol(Binary tree node)
Always provide a description for your symbols, and make your descriptions unique. If you use symbols for accessing private properties, treat their descriptions as if they were variable names.
Note that even if you pass the same description to two symbols, their value will still differ. Knowing the description does not make it possible for you to create the same symbol.
console.log( leftNode === rightNode );
> false

Global symbol registry

ES6 has a global resource for creating symbols: the symbol registry. The symbol registry provides us with a one-to-one relationship between strings and symbols. The registry returns symbols using Symbol.for( key ).
Symbol.for( key1 ) === Symbol.for( key2 ) whenever key1 === key2. This correspondance works even across service workers and iframes.
let privateProperty1 = Symbol.for( 'firstName' );
let privateProperty2 = Symbol.for( 'firstName' );

myObject[ privateProperty1 ] = 'Dave';
myObject[ privateProperty2 ] = 'Zsolt';

console.log( myObject[ privateProperty1 ] );
// Zsolt
As there is a one-to-one correspondance between symbol values and their string keys in the symbol registry, it is also possible to retrieve the key. Use the Symbol.keyFor method.
Symbol.keyFor( privateProperty1 );
> "firstName"

Symbol.keyFor( Symbol() );
> undefined

Symbols as “private” property keys

Creating truly private properties and operations is not an obvious task in JavaScript. Symbols provide one way to indicate that a property or an operation of a class is private:
const _width = Symbol('width');
class Square {
    constructor( width0 ) {
        this[_width] = width0;
    }
    getWidth() {
        return this[_width];
    }
}
As long as you can hide the _width constant, you should be fine. One option to hide _width is to create a closure:
let Square = (function() {

    const _width = Symbol('width');

    class Square {
        constructor( width0 ) {
            this[_width] = width0;
        }
        getWidth() {
            return this[_width];
        }
    }

    return Square;  

} )();
The advantage of this approach is that it becomes intentionally harder to access the private _width value of our objects. It is also evident which of our properties are intended to be public, an which are intended to be private.
The drawbacks are also obvious:
  • By calling Object.getOwnPropertySymbols, we can get access to the symbol keys. Therefore, private fields are not truly private
  • developer experience is also worse, as you have to write more code. Accessing private properties is not as convenient as in Java or TypeScript for example
Some developers will express their opinion on using symbols for indicating privacy. In practice, your team has the freedom of deciding which practices to stick to, and which rules to follow. If you agree on using symbols as private keys, it is a working solution, as long as you don’t start writing workarounds to publicly access private field values.
If you use symbols to denote private fields you have done your best to indicate that a property is not to be accessed publicly. When someone writes code violating this common sense intention, they should bear the consequences.
There are various methods for structuring your code such that you indicate that some of your variables are private in JavaScript. None of them looks as elegant as a private access modifier.
If you want true privacy, you can achieve it even without using ES6. One of the exercises deals with this topic. Try to solve it, or read the reference solution.
The question is not whether it is possible to simulate private fields in JavaScript. The real question is whether you want to simulate them or not. Once you figure out that you don’t need truly private fields for development, you can agree whether you use symbols, weak maps (see later), closures, or a simple underscore prefix in front of your variables.

Creating enum types

Enums allow you to define constants with semantic names and unique values. Given that the values of symbols are different, they make excellent values for enumerables.
const directions = {
    UP   : Symbol( 'UP' ),
    DOWN : Symbol( 'DOWN' ),
    LEFT : Symbol( 'LEFT' ),
    RIGHT: Symbol( 'RIGHT' )
};

Avoiding name clashes

When using symbols as identifiers for objects, we don’t have to set up a global registry of available identifiers. We also save creation of a new identifier, as all we need to do is create a Symbol().
The same holds for external libraries.

Well known symbols

There are some well known symbols defined to access and modify internal JavaScript behavior. You can do magic such as redefining built-in methods, operators, and loops.
It is cool to apply hacks to the language, but ask yourself, is this skill going to move you forward in your career?
We will not focus on well known symbols in this article. If there is a valid use case for it, I will signal it in the corresponding lesson. Otherwise, I suggest staying away from manipulating the expected behavior of your code.

Exercises

Exercise 1. What are the pros and cons of using an underscore prefix for expressing our intention that a field is private? Compare this approach with symbols!
let mySquare {
    _width: 5,
    getWidth() { return _width; }
}
Exercise 2. Find a way to simulate truly private fields in JavaScript!

News on ES6 in Practice

I have decided on moving the course to LeanPub. For simplicity, you can download the book and the workbook as one file, which is a change compared to the original package.
I will continuously update the Leanpub package. If you purchased the course from another platform, before releasing the next update, I will get in touch with you with a dedicated 100% off survey.
Talking about updates, expect an update on December 30th. There will also be a price increase on January 1st. The update on December 30th contains the following changes:
  • A revision of all existing chapters, exercises, and solutions
  • the Reflect API
  • Proxies
  • Extensions of the Math and Number objects
  • At least ten new exercises and solutions
Following the philosophy behind Leanpub, by purchasing ES6 in Practice, you will get all future updates free of charge.
Would you like to learn ES6?
Strengthen your JavaScript knowledge with marketable skills!
YesORNo
Close
Get the Course "ES6 in Practice"!
Learn Marketable Skills.

Verify your knowledge with real world exercises.
I'm In!
Close
Thank you for your subscription.
Please check your inbox to access the first lesson.
Close