Javascript Symbols + Classes = đź’–

Symbol is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that’s guaranteed to be unique. Symbols are often used to add unique property keys to an object that won’t collide with keys any o…


This content originally appeared on DEV Community and was authored by Lioness100

Symbol is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that’s guaranteed to be unique. Symbols are often used to add unique property keys to an object that won’t collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object.

In Javascript, Symbols are incredible resources for all sorts of use cases. However, I think many of the possibilities show their true colors when combined with classes. There are very many static Symbol properties that can be used with classes, although I'll only be going through a few of the most important. Check the rest out at the MDN page linked!

All of the below will work with any object, not just classes. I think classes simply demonstrate their usefulness the best

How to use static Symbol properties

As described in the top quote, symbols are unique. That means, if you create a symbol and attach it to an object as a property key (using bracket notation property accessors), the assigned value will only be accessible when using the same exact instance of that symbol.

const mySymbol = Symbol('foo');

const obj = {
  [mySymbol]: 'bar',
};

// undefined - 'foo' is only a descriptor
// and doesn't actually do anything
obj.foo;

// undefined - all symbols are unique
obj[Symbol('foo')]; 

// 'bar' - 🎉
obj[mySymbol];

With this mechanic, static Symbol properties were created (for mostly internal use) so that classes and objects can be more configurable without taking up any property names that you could use otherwise.

1. Symbol.iterator and Symbol.asyncIterator

Learn about iterators

This one's a biggie. Symbol.iterator and Symbol.asyncIterator will most notably specify the behavior of a class in for...of and for await...of loops respectively. Here's an example of it in action:

// a class representing a bookshelf
class Bookshelf {
  // this functions is an iterator,
  // so we prefix it with a `*`
  // and use the `yield` keyword
  *[Symbol.iterator]() {
    yield 'Harry Potter';
    yield 'The Tempest';
    yield 'The Lion King';
  }
}

In this example, we are using Symbol.iterator to create an iterator that will be used to iterate through every book on the "bookshelf". I hard coded the values, but it a more realistic example, you'd probably want to dynamically yield every value in a predefined array (i.e. this.books).

class Bookshelf {
  // ...
}

const bookshelf = new Bookshelf();
for (const book of bookshelf) {
  console.log(book);
}

The above will log the following:

'Harry Potter'
'The Tempest'
'The Lion King'

It's like magic! The same can be used for Symbol.asyncIterator with for await...of

2. Symbol.toStringTag

This symbol is much less confusing than the above, but still very cool. Ever wondered why Object#toString() returns '[object Object]', Map#toString() returns '[object Map]', etc?

Your first guess might be that it uses constructor.name. However, we can debunk that because the following doesn't work:

class Book{}

// '[object Object]' - not '[object Book]'
new Book().toString();

Instead, they use Symbol.toStringTag to specify what tag they want to be attached.

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

// '[object Book]'
new Book().toString();

Note that if you want your class to return something special when converted to a string that doesn't fit that format, you can simply overwrite the toString() method itself.

I'm sure there are many use cases for this, but I think it's best used for debugging (especially if you're creating a library and want to make it easy for the end user to troubleshoot). If you try to print some text and you find [object Object], it might be hard to find out what's causing it

However, if you get [object Boolean], [object Null], or a custom [object SomeClassName], I bet you it will be a lot easier.

3. Symbol.hasInstance

This symbol defines the behavior of instanceof when used with your class.

'hello world' instance of string; // true
100 instance of string; // false

String[Symbol.hasInstance]('hello world'); // true
String[Symbol.hasInstance](100); // false

Here's an example of implementing it yourself:

class Book {
  constructor(name, author) {
    this.name = name;
    this.author = author;
  }

  // `instance` is what's being compared
  static [Symbol.hasInstance](instance) {
    // `instance` is a `Book` if
    // it has a name and author
    return book.name && book.author;
  }
}

// these are the fields we need
const name = 'Harry Potter';
const author = 'J.K. Rowling';

new Book(name, author) instanceof Book; // true
{ name, author } instance of Book; // true

4. Symbol.species

This one's hard to wrap your head around. Symbol.species is most notably used internally for arrays and maps (although you can use it in your custom classes as well) to find what subclass should be created from methods that create new classes out of themselves... or something.

Talk is cheap — show me the code

Here's an example:

class CustomArray extends Array {}
const arr = new CustomArray(1, 2, 3);

// true - even though `Array#map` creates a *new* array,
// it will dynamically access the constructor through `this.constructor`,
// meaning it can automatically create derived classes when needed
console.log(arr.map((num) => num * 2) instanceof CustomArray);

But, maybe you want to override that:

class CustomArray extnds Array {
  static get [Symbol.species]() {
    return Array;
  }
}

const arr = new CustomArray(1, 2, 3);

// false - this will now always create `Array`s
console.log(arr.map((num) => num * 2) instanceof CustomArray);

Internally, arrays are deciding what class to consrtuct like so:

new (this.constructor[Symbol.species] || this.constructor)(/* ... */);

First it accesses Symbol.species to see if you have an override set up, then it falls back to the current constructor.

I hope you learned one or more new way to use the Symbol! If you have any questions, corrections, or addons, I would love to hear them. Peace ✌


This content originally appeared on DEV Community and was authored by Lioness100


Print Share Comment Cite Upload Translate Updates
APA

Lioness100 | Sciencx (2021-10-11T21:38:48+00:00) Javascript Symbols + Classes = đź’–. Retrieved from https://www.scien.cx/2021/10/11/javascript-symbols-classes-%f0%9f%92%96/

MLA
" » Javascript Symbols + Classes = đź’–." Lioness100 | Sciencx - Monday October 11, 2021, https://www.scien.cx/2021/10/11/javascript-symbols-classes-%f0%9f%92%96/
HARVARD
Lioness100 | Sciencx Monday October 11, 2021 » Javascript Symbols + Classes = đź’–., viewed ,<https://www.scien.cx/2021/10/11/javascript-symbols-classes-%f0%9f%92%96/>
VANCOUVER
Lioness100 | Sciencx - » Javascript Symbols + Classes = đź’–. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/10/11/javascript-symbols-classes-%f0%9f%92%96/
CHICAGO
" » Javascript Symbols + Classes = đź’–." Lioness100 | Sciencx - Accessed . https://www.scien.cx/2021/10/11/javascript-symbols-classes-%f0%9f%92%96/
IEEE
" » Javascript Symbols + Classes = đź’–." Lioness100 | Sciencx [Online]. Available: https://www.scien.cx/2021/10/11/javascript-symbols-classes-%f0%9f%92%96/. [Accessed: ]
rf:citation
» Javascript Symbols + Classes = đź’– | Lioness100 | Sciencx | https://www.scien.cx/2021/10/11/javascript-symbols-classes-%f0%9f%92%96/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.