JS Tip of the Day: Symbols for Safe Property Names

Symbols for Safe Property Names
Version: ES2015
Level: Intermediate

Symbols are a special kind of primitive value that represent a unique value. When you create a new symbol value, it will not equal anything else, even another symbol that was created the exact same way.

let original = Symbol('copy me');
let maybeACopy = Symbol('copy me');
console.log(original === maybeACopy); // false

Symbols, like strings, can be used as property keys in objects. Their uniqueness means that they are guaranteed not to conflict with any other property used in a given object.

let namedObject = { name: 'Bob' };
let name = Symbol('name');
namedObject[name] = 'Bobbert'; // <- uses symbol as key
console.log(namedObject.name); // Bob
console.log(namedObject[name]); // Bobbert

Using symbols like this can provide 2 kinds of safety:

  1. Helps prevent other users from overwriting your object properties
  2. Allows you to add properties to other objects without fear of overwriting it’s properties

For the first point, if you use a symbol property for an object that might be used by other users, then those users would not be able to overwrite that property accidentally with their own properties.

// your code
function getUserDefinedParamsObject () {
    let desc = Symbol('framework object description');
    return {
        [desc]: 'a user defined parameters object'
    };
}

// user code
let params = getUserDefinedParamsObject();
params.x = 3;
params.foo = 'bar';
params.desc = 'custom user description';
// ...

Here, an object is created for users to add their own properties. No matter what properties they choose, they will not overwrite the property defined by the symbol inside the function. While it’s not impossible for the user to get access to the symbol in this case (there are ways to get symbols out of objects that have them), this would require the user to do extra work specifically with the intention to cause problems and be a general nuisance.

Similarly, with respect to point 2, you as a user can also help prevent conflicts by using symbols for properties of objects that you didn’t create. HTML elements, for example, already have a large number of pre-existing property values, and different HTML elements can have different sets of properties. Additionally, with custom HTML elements, you have no idea what new or custom property keys may exist on any arbitrary element you pull from the DOM. If you want to add your own property to such an element, you might want to be sure you’re not accidentally overwriting something that’s already there. Symbols can allow you to easily do this.

let elem = document.getElementById('elem');
let meta = Symbol('metadata');
elem[meta] = 'If you can read this, you are a hacker, like me';

No matter what properties exist on the elem element, the meta symbol property is guaranteed not to collide with any of them allowing for a means to more safely add custom data to that element.

If you’re thinking you could just instead use a normal, yet very unique property name for cases like this, you’d be correct; that’s certainly an option.

let object = {};
object.__youll_never_catch_the_likes_of_me__ = 'so lonely';

But symbols also have the characteristic of being non-enumerable for normal object enumeration, keeping them out of loops like for...in. This can help hide them from other operations that interact with the object using these loops.

let object = {};
object.__youll_never_catch_the_likes_of_me__ = 'so lonely';
let hidden = Symbol('hidden');
object[hidden] = 'most lonely';

for (let key in object) {
    console.log('Found key: ' + key);
}
// Found key: __youll_never_catch_the_likes_of_me__

More info: