DEV Community

Cover image for JS Symbol, what the heck?
Romain Trotard
Romain Trotard

Posted on • Updated on

JS Symbol, what the heck?

Among the primitive data type, you may have already heard about Symbol. But you're asking yourself what is it? When is it useful? When are they currently used?
If it's the case, you are in the right place. It was my case few times ago :)

What is it?

Symbol is a new primitive data type introduced with ES6. It can provide us unique value by using directly Symbol(optionalDescriptiveText) or can share Symbol through the global Symbol registry.
Thanks to it we can add property to object being sure it doesn't conflict with another one.

Creation

Unique value

I already spoiled it in the previous part, you can create a unique Symbol value by using Symbol(optionalDescriptiveText):

const myFirstSymbol = Symbol('This is my first symbol');
Enter fullscreen mode Exit fullscreen mode

Watch out: the optional descriptive text is here just to add a description to the Symbol. It will not transform the string into a Symbol:

As I said previously a Symbol is unique:

// Will print false!!!
console.log(Symbol('Description') !== Symbol('Description'))
Enter fullscreen mode Exit fullscreen mode

Warning: you cannot instantiate it as you may do with String, Boolean or Number:

// Will show you in the console something like
// Uncaught TypeError: Symbol is not a constructor
new Symbol('Trying to make an object');
Enter fullscreen mode Exit fullscreen mode

Shared Symbol

You can also create Symbol to be shared through your application/library.
You can do it with: Symbol.for(key):

// Create a shared Symbol
const sharedSymbol = Symbol.for('Shared Symbol');

// You can assert that you can get this Symbol

// Will print true
console.log(sharedSymbol === Symbol.for('Shared Symbol'));
Enter fullscreen mode Exit fullscreen mode

Note: You can create a shared Symbol with undefined / no key

// Will print true
console.log(Symbol.for() === Symbol.for(undefined));
Enter fullscreen mode Exit fullscreen mode

Note: there is a keyFor method accessible from Symbol to retrieve the key of a shared Symbol:

const sharedSymbol = Symbol.for("Key of shared symbol");

// Will print: "Key of shared symbol"
console.log(Symbol.keyFor(sharedSymbol));
Enter fullscreen mode Exit fullscreen mode

Not gonna lie, I don't know in which situation we would like to retrieve the key of a shared Symbol. If you know some use cases, do not hesitate to put it in commentaries :)


Okay, now that we have seen how to create a Symbol, let's some properties that have Symbols.

Properties

Not enumerable

When adding in objet a Symbol as key, the property will not be enumerable:

const person = {
  firstName: "Bob",
  lastName: "Sponge",
  [Symbol("secret")]: "I was created by a marine biologist",
};

// Will print
// Key: "firstName" and value: "Bob"
// Key: "lastName" and value: "Sponge"
Object.entries(person).forEach(([key, value]) =>
  console.log(`Key: "${key}" and value: "${value}"`)
);
Enter fullscreen mode Exit fullscreen mode

Same value in iframe

There is something pretty unpredictable that happens. Each iframe has its own realm so its own instance of Symbol. However, shared Symbol are the same through realm.

Let's make an iframe in which we declare a shared Symbol:

<iframe
  srcdoc="<script>
              var sharedSymbol = Symbol.for('Shared symbol');
              </script>"
></iframe>
Enter fullscreen mode Exit fullscreen mode

Now let's get this iframe and get the window from it through the contentWindow property:

const iframe = document.querySelector("iframe");
const iframeWindow = iframe.contentWindow;

// Will print false!
console.log(iframeWindow.Symbol === Symbol);

// But will print true!
console.log(
  iframeWindow.sharedSymbol === Symbol.for("Shared symbol")
);
Enter fullscreen mode Exit fullscreen mode

Note: Thanks to it features added by well-known Symbols work through realms. For example for...of on iterable objects.

Note: var is accessible through the window object because it has global scope. Let's see my article Differences between var, let and const for more explanations.

Current usage: well-known Symbols

There are some well-known Symbols that are used to implement methods that you use everyday.

Note: These well-known Symbols are static properties of Symbol and are referenced with the notation @@name that corresponds to Symbol.name.

Let's see a few:

  • Symbol.iterator: This symbol defines the default iterator for an object that will make the use of for...of possible. The object will be then iterable.

For example, if we have an Array of Person with the type:

type Person = {
  firstName: string;
  lastName: string;
}
Enter fullscreen mode Exit fullscreen mode

And when we loop on this Array, we want to get directly the template ${firstName} ${lastName}. The code will be:

const persons = [
  { lastName: "Spears", firstName: "Britney" },
  {
    lastName: "Grande",
    firstName: "Ariana",
  },
  {
    lastName: "Timberlake",
    firstName: "Justin",
  },
];

persons[Symbol.iterator] = function () {
  let index = 0;
  return {
    next: () => {
      const hasNext = this.length > index;

      if (hasNext) {
        const person = this[index++];

        return {
          done: false,
          value: `${person.firstName} ${person.lastName}`,
        };
      } else {
        return {
          done: true,
        };
      }
    },
  };
};

// This will print
// Britney Spears
// Ariana Grande
// Justin Timberlake
for (let person of persons) {
  console.log(person);
}
Enter fullscreen mode Exit fullscreen mode

Note: It's possible to implement it with generators to make it "simpler". But not to lost people I decided to code it "manually".


  • Symbol.hasInstance: This symbol manages the configuration of the instanceof operator for a class.

For example, let's imagine we have two classes Building and House.
We want new House() instanceof Building to return true. We can do this:

class Building {
  constructor() {
    this.type = "building";
  }

  static [Symbol.hasInstance](instance) {
    return (
      instance.type === "house" ||
      instance.type === "building"
    );
  }
}

class House {
  constructor() {
    this.type = "house";
  }

  static [Symbol.hasInstance](instance) {
    return instance.type === "house";
  }
}

// Will print true
console.log(new House() instanceof Building);
// Will print false
console.log(new Building() instanceof House);
Enter fullscreen mode Exit fullscreen mode

Note: In real world, we would not do this but just:

class Building {}

class House extends Building {}
Enter fullscreen mode Exit fullscreen mode

  • Symbol.split: This symbol can be used as a method and will be called by the String's split method:

For example, we can define a WordSplit class that will split a phrase with space:

class WordSplit {
  [Symbol.split](string) {
    return string.split(" ");
  }
}

console.log(
  "A phrase that will be splitted".split(new WordSplit())
);
Enter fullscreen mode Exit fullscreen mode

  • Symbol.toStringTag: The symbol can be used to defined an object's property that returns a string that will be used to describe the object. This method is called by the Object's toString method:
class Computer {
  constructor() {
    this[Symbol.toStringTag] = "Computer";
  }
}

// Will print [object Computer]
console.log(new Computer().toString());
Enter fullscreen mode Exit fullscreen mode

Note: Otherwise it would print [object Object]


Conclusion

We just see together what Symbol is, its properties and where they are currently used. I hope it's now clear for you what are Symbols and where they are currently used in everyday life features. So do not tell yourself that Symbol are not useful anymore (if it was the case) :)

Fun fact, React uses Symbol to tag the type of elements through the property $$typeof: see code.


Do not hesitate to comment and if you want to see more, you can follow me on Twitter or go to my Website. 🐼

Discussion (8)

Collapse
jonrandy profile image
Jon Randy

Great explanation! Another use case for Symbols is safely extending prototypes (Array, String, Number, etc.) - I built a library (Metho) that helps to do this kind of thing easily. Check it out if you want to see another way Symbols can be used

Collapse
romaintrotard profile image
Romain Trotard Author

Oh thank you for the additional information. I check your library asap :)

Collapse
jzombie profile image
jzombie • Edited on

Great article! Really enjoyed the iterator and class toString methods you presented.

Collapse
romaintrotard profile image
Romain Trotard Author

Thank you. Glad you liked it :)

Collapse
zyabxwcd profile image
Akash • Edited on

one question.
why did you write the last example as
class Computer {
constructor() {
this[Symbol.toStringTag] = "Computer";
}
}
and not like
class Computer {
[Symbol.toStringTag] = "Computer";
}

Collapse
romaintrotard profile image
Romain Trotard Author

I write like this because if I'm not mistaken the public class field will be available in ES2022.
Or we currently can use it thanks to babel plugin @babel/plugin-proposal-class-properties

Collapse
zyabxwcd profile image
Akash

wow. you used some advanced syntax and techniques there. I read about Symbols way back but never used them and neither do people generally know about them as I have seen.

Collapse
romaintrotard profile image
Romain Trotard Author

Yep generally people doesn't know about Symbol, hoping my article will show that it's useful :)