Skip to content

Instantly share code, notes, and snippets.

@juanmaguitar
Last active September 28, 2016 10:24
Show Gist options
  • Save juanmaguitar/2debcc7d8f81210a3c3d3dc1a0a9ce99 to your computer and use it in GitHub Desktop.
Save juanmaguitar/2debcc7d8f81210a3c3d3dc1a0a9ce99 to your computer and use it in GitHub Desktop.
Symbols (ES2015 Notes)

Symbols (ES2015 Notes)

The symbol is a new primitive type, a unique token that is guaranteed never to clash with another symbol → a kind of UUID (Universally Unique Identifier)

ES2015 symbols are values, but they’re not strings. They’re not objects. They’re something new: a seventh type of value.

The primary reason for the introduction of symbols seems to have been to facilitate adding new functionality to the language without breaking existing code (hidden and unique properties)

Con los symbol podemos añadir propiedades ocultas y unicas a los objetos que nos permiten extender objetos facilmente.

Permiten a los desarrolladores añadir hooks sobre algunos objetos a traves de su API (similar a los hooks de wordpress)

// create a unique symbol
var isMoving = Symbol("isMoving");

// ...
// element[isMoving] = true;
// delete element[isMoving]

if (element[isMoving]) {
  smoothAnimations(element);
}

Tambien podemos modificar comportamientos de los datos al aplicarles metodos internos ( instanceof, toString, etc...)

Symbols are added to JavaScript to make the job of creating unique properties easier and also to make a number of default options of the language customizable. They bring in the ability of meta programming to the language.

While writing an application of considerable size in any language, at some point, we need a way to store certain intermediary data in a special property or, a special object. This special property or object shouldn’t be easily accessible to the rest of the application to avoid any accidental reassignment of a value. The most common way in use in JavaScript applications is by creating a string with current timestamp attached to it. Though we are able to create a unique property name in this way, the data is not secured. The property is easily enumerable. So someone can easily read the value and change it.

https://hashnode.com/post/what-is-the-purpose-of-symbol-in-es6-cisvy1fir0b9wf953flsnm99m

## Creating Symbols

  • To create symbols we call the Symbol function. Trying to call it with the new operator will result in a TypeError.

  • The Symbol function, will return a new and completely unique value.

> let foo = Symbol();
> let bar = Symbol();
> foo === bar
false

> let fizz = new Symbol()
VM188:1 Uncaught TypeError: Symbol is not a constructor()(anonymous function) @ VM188:1
  • Symbols can also be created with a label, by passing a string as the first argument → the label does not affect the value of the symbol, but is useful for debugging
> let foo = Symbol('baz');
> let bar = Symbol('baz');
> foo === bar
false
> console.log(foo);
Symbol(baz)

## What Can I Do With Them?

Good replacement for strings or integers as class/module constants:

class Application {
  constructor(mode) {
    switch (mode) {
      case Application.DEV:
        // Set up app for development environment
        break;
      case Application.PROD:
        // Set up app for production environment
        break;
      default:
        throw new Error('Invalid application mode: ' + mode);
    }
  }
}

Application.DEV = Symbol('dev');
Application.PROD = Symbol('prod');

// Example use
let app = new Application(Application.DEV);

As object property keys because:

  1. that symbol-based keys will never clash
  2. they won’t be enumerated in for..in loops, and are ignored by functions such as Object.keys(), Object.getOwnPropertyNames() and JSON.stringify() → This makes them ideal for properties that you don’t want to be included when serializing an object.
> let user = {};
> let email = Symbol();

> user.name = 'Fred';
> user.age = 30;
> user[email] = '[email protected]';

> Object.keys(user);
["name", "age"]
> Object.getOwnPropertyNames(user);
["name", "age"]
> JSON.stringify(user);
"{"name":"Fred","age":30}"

...but this doesn't guarantee privacy: Object.getOwnPropertySymbols() returns an array of any symbol-based keys, while Reflect.ownKeys() will return an array of all keys, including symbols.

> Object.getOwnPropertySymbols(user);
[Symbol()]
> Reflect.ownKeys(user)
["name", "age", Symbol()]

Well-known Symbols

Because symbol-keyed properties are effectively invisible to pre-ES6 code, they are ideal for adding new functionality to JavaScript’s existing types without breaking backwards compatibility.

The so-called ‘well-known’ symbols are predefined properties of the Symbol function that are used to customize the behavior of certain language features, and are used to implement new functionality such as iterators.

Symbol.iterator

Symbol.iterator is a well-known symbol which is used to assign a special method to objects which allows them to be iterated over.

> let band = ['Freddy', 'Brian', 'John', 'Roger'];
> let iterator = band[Symbol.iterator]();

> iterator.next()
Object {value: "Freddy", done: false}
> iterator.next()
Object {value: "Brian", done: false}
> iterator.next()
Object {value: "John", done: false}
> iterator.next()
Object {value: "Roger", done: false}
> iterator.next()
Object {value: undefined, done: true}

Try it in repl.it

The built-in types String, Array, TypedArray, Map and Set all have a default Symbol.iterator method which is called when an instance of one of these types is used in a for...of loop, or with the spread operator

Symbol.hasInstance

It is a method that can be used to customize the way instanceof operator works with a type. By default, instanceof checks if the object is an instance of the type referred and returns false if it doesn’t satisfy the condition. We can customize this behavior using Symbol.hasInstance.

class Person {
   constructor() {
       this.name = "Ravi";
       this.city = "Hyderabad";
   }

   static [Symbol.hasInstance](value) {
       const isObject = typeof(value) == "object";
       const hasName = "name" in value;
       const hasCity = "city" in value;
       console.log(value)
       if(value && isObject && hasName && hasCity) {
           return true;
       }

       return false;
   }
}

> var p = new Person();
> var p2 = {name: "Ram", city: "Chennai"};

> p instanceof Person
Person {name: "Ravi", city: "Hyderabad"}
true
> p2 instanceof Person
Object {name: "Ram", city: "Chennai"}
true

Symbol.toPrimitive

This method can be used to customize the way an object gets converted to a primitive type. JavaScript tries to convert an object to a primitive type when the object is used in an operation with a value of a primitive type.

var city = {
    name:"Hyderabad",
    temperature: 40,
    [Symbol.toPrimitive](hint){
        console.log(hint);
        switch(hint){
            case "number": return this.temperature;
            case "string": return this.name;
            default: return this;
        }
    }
};

> city*2
number
80
> "City is ".concat(city)
string
"City is Hyderabad"

### Symbol.toStringTag

This method is used to customize the way toString method behaves in an object. A call to toString on an object results either in name of the type of which the object is an instance, or just Object if the object is created without using any type. The toStringTag method can be used to change the way the method works.

> var a = {}

> a.toString()
"[object Object]"
> a+""
"[object Object]"

> a[Symbol.toStringTag] = "My-Super-Object"

> a.toString()
"[object My-Super-Object]"
> a+""
"[object My-Super-Object]"

Symbol.match, Symbol.replace, Symbol.search and Symbol.split

The regular expression methods of String are:

These methods forward the calls to the corresponding method on their regular expression parameter.

The actual implementation of these methods is in the regular RegExp class and names of the methods are values of these Symbols:

> class Expression {  
  constructor(pattern) {
    this.pattern = pattern;
  }
  [Symbol.match](str) {
    return str.includes(this.pattern);
  }
  [Symbol.replace](str, replace) {
    return str.split(this.pattern).join(replace);
  }
  [Symbol.search](str) {
      return str.indexOf(this.pattern);
  }
  [Symbol.split](str) {
      return str.split(this.pattern);
  }
}

> let sunExp = new Expression('sun');

> 'sunny day'.match(sunExp);
true
> 'rainy day'.match(sunExp);
false
> 'sunny day'.replace(sunExp, 'rainy');
"rainyny day"
> "It's sunny".search(sunExp);
5
> "daysunnight".split(sunExp);  
["day", "night"]

### Symbol.isConcatSpreadable

This is a property that can be used to configure behavior of the concat operation on arrays.

When two arrays are concatenated, the second array is spread and its elements are added as individual items in the resultant array.

By setting the property Symbol.isConcatSpreadable to false on the array, we can prevent spreading of the second array and the whole array will be added as an object entry in the resultant array.

> let arr1 = [1,2];
> arr1.concat([3,4], 5);
[1, 2, 3, 4, 5]

> let arr2 = [1,2];
> arr2[Symbol.isConcatSpreadable] = false;
> arr2.concat([3,4], 5); // [[1,2], 3, 4, 5]
[Array[2], 3, 4, 5] 

## The Global Registry

The specification also defines a runtime-wide symbol registry, which means that you can store and retrieve symbols across different execution contexts, such as between a document and an embedded iframe or service worker.

Symbol.for(key)

  • retrieves the symbol for a given key from the registry.
  • if a symbol does not exist for the key, a new one is returned → subsequent calls for the same key will return the same symbol.

Symbol.keyFor(symbol)

  • allows you to retrieve the key for a given symbol.
  • calling the method with a symbol that does not exist in the registry returns undefined.
> let debbie = Symbol.for('user');
> let mike   = Symbol.for('user');

> debbie === mike
true
> Symbol.keyFor(debbie);
"user"

Three sets of symbols

Summarizing: There are three ways to obtain a symbol.

  • Call Symbol() → returns a new unique symbol each time it’s called.

  • Call Symbol.for(string) → This accesses a set of existing symbols called the symbol registry. Symbols in the symbol registry are shared. If you call Symbol.for("cat") thirty times, it will return the same symbol each time.

  • Use symbols like Symbol.iterator, defined by the standard. A few symbols are defined by the standard itself. Each one has its own special purpose.


### More info

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment