Is Symbol really useful in JavaScript?

Symbols, the underestimated feature introduced in ES2015. Does it have any other purpose than interview questions?

Kemal Erdem (burnpiro)
ITNEXT
Published in
4 min readAug 9, 2019

--

Quick recap, what are those “Symbols” anyway?

primitive value that represents a unique, non-String Object property key

That’s Symbol definition from the current spec. But what does it mean? You probably know other primitive types in JS ( Undefined, Null, Boolean, Number, BigInt or String). Symbol is another one. I know that is not much and it sounds like defining recursion:

To understand recursion, you must first understand recursion

In programming languages, all primitive types are a just bunch of bytes stored in memory. It doesn’t matter if it’s a string or number, from a data perspective it’s still just bytes. In the case of symbols, they are tokens that serves as unique IDs.

How to use Symbol

// string "id" is a Symbol's description
const id = Symbol('id');

// you can also create Symbol without description
const noDescriptionId = Symbol();

We’ve just created id which is a Symbol. But the important thing is that id !== Symbol('id'). Like I've said earlier Symbols are unique.

Unless they aren’t…

There is another way to create Symbol, and it’s called

Symbol.for('id');

assert(Symbol.for('id') === Symbol.for('id')); // true

OK, what happened here? We’ve just used global Symbol registry to store our Symbol. As the name says it's a global registry and global in this case is also cross-realm (in JS that mean Symbol created inside an iframe and is the same as in your current execution context).

Aside note: You can check if Symbol is unique or not. For that, you can use Symbol.keyFor(yourSymbol). If yourSymbol is global, then it returns the Symbol's description ( id) as a string, else it returns undefined.

assert(Symbol.keyFor(Symbol.for('id')) === 'id');
assert(Symbol.keyFor(Symbol('id')) === undefined);

Properties you need to know

  • Symbol will never conflict with Object key. You can use Symbol as the object key store[Symbol.for('id')] = 42.
  • Keys created using Symbol is not iterable. So when you call Object.values(store) you won't get 42 unless there is another key (not Symbol key) with that value. That's a really useful property because it won't change library behavior when you add another property.
  • To extract Symbols from the object, you can use Object.getOwnPropertySymbols().
  • Symbols are copied to other objects. Every enumerable Symbol is copied from obj a into obj b when Object.assign(a, b) is called.

Symbol’s usefulness

Now when you know what a Symbol is, we can discuss why should you consider Symbols useful? Let’s suppose you’re creating a library and want to give your user a possibility to extend your library.

Your library is called stateOfTheArtValidation ( stav to make it short). And it exports the list of available extensions you can assign to your object.

export const extensibleSymbols = {
VALIDATION: Symbol('validationFun'),
REQUIRED: Symbol('required'),
};

Now we can use any of those Symbols in our objects.

const myObj = {
someProp: 'anyValue',
[stav.Symbols.VALIDATION]: element => element.hasOwnProperty('someProp'),
};

Let me first show you what your library does with that before we discuss it.

// somewhere in our library
validate: (...objectsToValidate) => {
const validations = [];

for (const objToValidate of objectsToValidate) {
if (typeof objToValidate[this.Symbols.VALIDATION] === 'function') {
validations.push({
result: objToValidate[this.Symbols.VALIDATION](objToValidate),
});
} else {
validations.push({
result: this.standardValidation(objToValidate),
});
}
}

return validations;
};

validate is a method from your library. But there are some cases when you want to give users the option to apply their validation instead of your standardValidation method. Instead of defining a list of string properties which user can use to attach their validation method, you've defined Symbol for it. That way there is a 0% chance to have a conflict with any of the existing keys on that object, so a user cannot accidentally overwrite the property you want to use.

Ofc that example is not really useful IRL but you get an idea.

Well-Known Symbols

Well-known symbols are built-in Symbol values that are explicitly referenced by algorithms of this specification.

Someone already thought about that by creating built-in Symbols in JS. Those Symbols are useful to overwrite/add functionalities of/to objects. For instance, you can use Symbol.iterator to define iterator and enable your object to be iterable in the way you want.

const myObj = {
test: 'test',
};

myObj[Symbol.iterator] = function* myGenerator() {
yield this.test;
yield 'See ya!';
};

for (const val of myObj) {
console.log(val);
}

Prints:

test
See ya!

Conclusion

Now you understand how powerful and useful Symbols might be. Probably you’re going to use built-in Symbols more often than defining your own. But library creators (like you :P ) have another way for users to extend library functionality.

Originally published at https://erdem.pl.

--

--