Build-ins - Symbols and Protocols in ES6

Primitive data types

Symbols are new addition in ES6 to primitive data types. Previously numbers, strings, boolean, null, undefined were primitive data types.

Symbol Symbol is just a unique identifier. Its most often used to uniquely identify properties within an object. A symbol is a unique and immutable data type that is often used to identify object properties.

To create a symbol, you write Symbol() with an optional string as its description.

const sym1 = Symbol('apple');
console.log(sym1);
Symbol(apple)

This will create a unique symbol and store it in sym1. The description "apple" is just a way to describe the symbol, but it can’t be used to access the symbol itself.

const sym2 = Symbol('banana');
const sym3 = Symbol('banana');
console.log(sym2 === sym3);
false

…then the result is false because the description is only used to describe the symbol. It’s not used as part of the symbol itself—each time a new symbol is created, regardless of the description.

const bowl = {
  'apple': { color: 'red', weight: 136.078 },
  'banana': { color: 'yellow', weight: 183.15 },
  'orange': { color: 'orange', weight: 170.097 }
};

The bowl contains fruit which are objects that are properties of the bowl. But, we run into a problem when the second banana gets added.

const bowl = {
  'apple': { color: 'red', weight: 136.078 },
  'banana': { color: 'yellow', weight: 183.151 },
  'orange': { color: 'orange', weight: 170.097 },
  'banana': { color: 'yellow', weight: 176.845 }
};
console.log(bowl);
Object {apple: Object, banana: Object, orange: Object}

Instead of adding another banana to the bowl, our previous banana is overwritten by the new banana being added to the bowl. To fix this problem, we can use symbols.

const bowl = {
  [Symbol('apple')]: { color: 'red', weight: 136.078 },
  [Symbol('banana')]: { color: 'yellow', weight: 183.15 },
  [Symbol('orange')]: { color: 'orange', weight: 170.097 },
  [Symbol('banana')]: { color: 'yellow', weight: 176.845 }
};
console.log(bowl);
Object {Symbol(apple): Object, Symbol(banana): Object, Symbol(orange): Object, Symbol(banana): Object}

By changing the bowl’s properties to use symbols, each property is a unique Symbol and the first banana doesn’t get overwritten by the second banana.

Two new protocols in ES6:

  • the iterable protocol
  • the iterator protocol

These protocols aren’t built-ins, but they will help you understand the new concept of iteration in ES6, as well as show you a use case for symbols.

The Iterable Protocol The iterable protocol is used for defining and customizing the iteration behavior of objects. What that really means is you now have the flexibility in ES6 to specify a way for iterating through values in an object. For some objects, they already come built-in with this behavior. For example, strings and arrays are examples of built-in iterables.

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (const digit of digits) {
  console.log(digit);
}
0
1
2
3
4
5
6
7
8
9

Any object that is iterable can use the new for...of loop.

How it Works

In order for an object to be iterable, it must implement the iterable interface. Basically means that in order for an object to be iterable it must contain a default iterator method. This method will define how the object should be iterated. The iterator method, which is available via the constant [Symbol.iterator], is a zero arguments function that returns an iterator object. An iterator object is an object that conforms to the iterator protocol.

The Iterator Protocol

The iterator protocol is used to define a standard way that an object produces a sequence of values. What that really means is you now have a process for defining how an object will iterate. This is done through implementing the .next() method.

How it Works An object becomes an iterator when it implements the .next() method. The .next() method is a zero arguments function that returns an object with two properties:

value : the data representing the next value in the sequence of values within the object done : a boolean representing if the iterator is done going through the sequence of values If done is true, then the iterator has reached the end of its sequence of values. If done is false, then the iterator is able to produce another value in its sequence of values.

const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const arrayIterator = digits[Symbol.iterator]();

console.log(arrayIterator.next());
console.log(arrayIterator.next());
console.log(arrayIterator.next());
Object {value: 0, done: false}
Object {value: 1, done: false}
Object {value: 2, done: false}