JavaScript magic with Symbols
Tags: javascript, beginners, webdev, web
Published: 2020-05-05 10:02:10

---
title: JavaScript magic with Symbols
published: true
tags: #javascript #beginners #webdev #web
---

**Q**: How to implement object `magic` which has the following behavior?
```js
const magic = {};

console.log(2 + +magic); // 42
console.log(5 + magic); // 1337
console.log(`JavaScript is ${magic}`) // "JavaScript is awesome"
console.log(magic.toString()); // "[object magic]"
```
The question is very interesting and you are probably thinking "what kind of sorcery is this 😱?". To solve this mystery we need to learn about Symbols in JavaScript and see how they can help us in this case.

## Symbols in JavaScript
A `symbol` is a primitive data type introduced in ES6. It's created with `Symbol` function and globally unique. Symbols can be used as object properties to provide uniqueness level access to objects and as hooks into built-in operators and methods, enabling us to alter the default behavior of JavaScript.
```js
const mySymbol = Symbol('mySymbol');
typeof mySymbol // "symbol"

Symbol('mySymbol') === Symbol('mySymbol') // false
```

## Symbols as object properties
Since symbols are globally unique, they can be used in a situation where there is a risk of property name collision. Imagine you are working on a library and need to attach your lib metadata to the supplied object.
```js
const magic = {};

function someLibFunction(obj){
   obj.meta = 'MyLibMeta';
}

someLibFunction(magic);

console.log(magic); // { meta: 'MyLibMeta' }
```
There is a problem with this code because the `meta` property could be overwritten by the user code or other library.
```js
const magic = {};

function someLibFunction(obj){
   obj.meta = 'MyLibMeta';
}

function userFunction(obj){
   obj.meta = 'I use this for my code';
}

someLibFunction(magic);
userFunction(magic);

console.log(magic); // { meta: 'I use this for my code' }
```
Now, `userFunction` has overwritten the `meta` property and lib is not working properly. Lib writers can use symbols for property names to avoid name collisions with other code.
```js
const magic = {};

const libMetaSymbol = Symbol('meta');

function someLibFunction(obj){
   obj[libMetaSymbol] = 'MyLibMeta';
}

function userFunction(obj){
   obj.meta = 'I use this for my code';
}

someLibFunction(magic);
userFunction(magic);

console.log(magic[libMetaSymbol]); // 'MyLibMeta'
console.log(magic.meta); // 'I use this for my code'
```

Symbols as properties are not available through `Object.keys`, but rather through `Reflect.ownKeys`. This is for the purpose of backward compatibility because the old code doesn't know about symbols.
Keep in mind that `Reflect.ownKeys` returns all property names and symbols. If you need to read only symbols, use `Object.getOwnPropertySymbols()`.

```js
const magic = { id: 1 };
const metaSymbol = Symbol('meta');

magic[metaSymbol] = 'MyMeta';

console.log(Object.keys(magic)); // ["id"]
console.log(Reflect.ownKeys(magic)); // ["id", [object Symbol] { ... }]
console.log(Object.getOwnPropertySymbols(magic)); // [[object Symbol] { ... }]
```

## Well-known symbols
Well-known symbols are defined as static properties on `Symbol` object.
They are used by built-in JavaScript functions and statements such as `toString()` and `for...of`. `toString()` method uses `Symbol.toStringTag` and `for...if` uses `Symbol.iterator`. There are many more built-in symbols and you can read about them <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol">here</a>.

To solve the magic object question, we need to look closer at `Symbol.toPrimitive` and `Symbol.toStringTag` symbols.

### Symbol.toPrimitive
JavaScript calls the `Symbol.toPrimitive` method to convert an object to a primitive value. The method accepts `hint` as an argument, hinting at what kind of conversion should occur. `hint` can have a value of `string`, `number,` or `default`. There is no `boolean` hint since all objects are `true` in boolean context.

### Symbol.toStringTag
Property used internally by `Object.prototype.toString()` method. You would assume that string template literals also call `Symbol.toStringTag` under the hood, but that's not the case. Template literals call `Symbol.toPrimitive` method with a `string` hint.

## Answering the question
Now when we know a lot about symbols, let's see the answer to the magic object question.

```js
const magic = {
  [Symbol.toPrimitive](hint) {
    if (hint == 'number') {
      return 40;
    }
    if (hint == 'string') {
      return 'awesome';
    }
    return 1332;
  },

  get [Symbol.toStringTag]() {
    return 'sorcery';
  }
}

console.log(2 + +magic); // 42
console.log(5 + magic); // 1337
console.log(`JavaScript is ${magic}`) // "JavaScript is awesome"
console.log(magic.toString()); // "[object sorcery]"
```
First `console.log` converts `magic` to a number and adds 2. Conversion to number internally calls `Symbol.toPrimitive` function with hint `number`.

Second `console.log` adds magic to 5. Addition internally calls `Symbol.toPrimitive` function with hint `default`.

Third `console.log` uses `magic` with string template literals. Conversion to `string`, in this case, calls `Symbol.toPrimitive` function with hint `string`.

Final `console.log` calls `toString()` method on `magic` object. `toString()` internaly calls `Symbol.toStringTag` property.

## Conclusion
Symbols are globally unique primitive types which allow us to avoid property name collision and hook into JavaScript internals. If you want to read more about symbols, visit <a href="https://tc39.es/ecma262/#sec-symbol-objects" target="_blank">EcmaScript specs </a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol" target="_blank">Mozzila docs</a>. 

Do you find symbols to be useful in your everyday programming?