Table of contents
EcmaScript 6 gave us something new and special called
I do not know how it was in your case but from the beginning of ES6 I didn't use
Basic usage and main definition.
Let’s firstly consider what says MDN Web Docs about Symbol:
The
- We have a new factory function
Symbol() . - We have new primitive type
symbol . - We do not need to use
new keyword in order to get newSymbol .
But we do not believe we want to check:
let symbolValue = Symbol(); // here we are creating new symbol using Symbol() function
console.log(typeof symbolValue); // will be "symbol"
// and now we should try to use new keyword in order to be sure that we can't create new Symbol in this way
try {
let symbolWithNewKeyword = new Symbol();
} catch(err) {
console.log(err.name); // "TypeError"
console.log(err.message); // "Symbol is not a constructor"
} // on this level we should understand and interpret Symbol() more as function but not as constructor
A few more important points:
- Symbol can take as parameter key.
- Every symbol value returned from
Symbol is unique (currently we are not talking aboutSymbol.for(name) ).
let symbol1 = Symbol('Some Description')
, symbol2 = Symbol('Some Description');
console.log(symbol1); // "Symbol(Some Description)"
console.log(symbol1 === symbol2); // here we will get `false`
Global Symbols and static functions .for(key) and .keyFor(symbol)
Symbol provides two static functions
Let’s take a look on them:
let symbol1 = Symbol.for('globalSymbol')
, symbol2 = Symbol.for('globalSymbol');
console.log(symbol1 === symbol2); // true
console.log(Symbol.for('globalSymbol') === Symbol.for('globalSymbol')); // true
great, now we can create global symbol but what about
let globalSymbol = Symbol.for('foo'); // global symbol
console.log(Symbol.keyFor(globalSymbol)); // "foo"
what we need to understand and take in account is that
let localSymbol = Symbol('Hello');
console.log(Symbol.keyFor(localSymbol)); // undefined
Well-known symbols
We can have not only custom but as well built in symbols representing internal language behaviors. And they are called well-known symbols. We will explore them one by one.
Symbol.iterator with Iterable and Iterator protocol
Iterable protocol - allows JavaScript objects to define or customize their iteration behavior,
such as what values are looped over in a
Iterator protocol - The iterator protocol defines a standard way to produce a sequence of values.
But here object should have a
Here is an example of
let range = {
from: 1,
to: 10
}
range[Symbol.iterator] = function() {
let current = this.from
, last = this.to;return {
next() {
return (current <= last)
? { done: false, value: current++ }
: { done: true };
}
};
};
for (let num of range) {
console.log(num); // 1, 2, 3, 4, 5, ...
}
or we can use built in with the following construction:
let range = {
from: 1,
to: 10,
[Symbol.iterator]: function() {
return this;
},
next() {
this.current = this.current || this.from;
return (this.current <= this.to)
? { done: false, value: this.current++ }
: { done: true };
}
}
for (let num of range) {
console.log(num); // 1, 2, 3, 4, 5, ...
}
Result will be the same.
But what is more interesting is that we can use as well spread operator
(if you do not know what is spread operator you can find more information here).
console.log(...range); // 1, 2, 3, 4, 5, ...
Symbol.search
Definition: Specifies the method that returns the index within a string that matches the regular expression.
First we need to define some mock data:
let user1 = {id: 1, name: 'Liza'}
, user2 = {id: 2, name: 'John'};
Example 1: Using class construction
class IsJohn {
[Symbol.search](valueToCheck) {
return valueToCheck === 'John';
}
}
console.log('Liza'.search(new IsJohn())); // false
console.log('John'.search(new IsJohn())); // true
Example 2: Using static variables on constructor function
function IsJohn() {}
IsJohn[Symbol.search] = (valueToCheck) => valueToCheck === 'John';
let user1 = {id: 1, name: 'Liza'}
, user2 = {id: 2, name: 'John'}
console.log('Liza'.search(IsJohn)); // false
console.log('John'.search(IsJohn)); // true
Example 3: Using Prototype construction
function IsJohn() {}
IsJohn.prototype[Symbol.search] = valueToCheck => valueToCheck === 'John';
console.log('Liza'.search(new IsJohn)); // false
console.log('John'.search(new IsJohn)); // true
Example 4: Using pure object
let IsJohn = {
[Symbol.search]: (valueToCheck) => valueToCheck === 'John'
}
console.log('Liza'.search(IsJohn)); // false
console.log('John'.search(IsJohn)); // true
Symbol.toStringTag
Definition: Is a string valued property that is used in the creation of the default string description of an object.
function classOf(val) {
return Object.prototype.toString.call(val).slice(8, -1);
}
class CustomClass {
get [Symbol.toStringTag]() {
return 'NewCustomClassName';
}
}
console.log(classOf(new CustomClass())); // "NewCustomClassName"
console.log(''+new CustomClass()); // "[object NewCustomClassName]
Symbol.unscopables
Definition: Is used to specify an object value of whose own and inherited property
names are excluded from the
let obj = {
prop1: 'Hello'
};
obj[Symbol.unscopables] = {
prop1: true
};
with (obj) {
console.log(prop1); // Error: prop1 is not defined
}
Symbol.isConcatSpreadable
Definition: Is used to configure if an object should be flattened to its array elements when using the
let arr1 = ['a', 'b', 'c'];
let arr2 = [1, 2, 3];
let concatenatedArr = arr1.concat(arr2);
console.log(concatenatedArr); // ["a", "b", "c", 1, 2, 3]
arr1[Symbol.isConcatSpreadable] = false;
concatenatedArr = arr1.concat(arr2);
console.log(concatenatedArr); // [["a", "b", "c"], 1, 2, 3]
Symbol.toPrimitive
Definition: Specifies a function valued property that is called to convert an object to a corresponding primitive value.
let date = {
value: new Date(),
days: [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
],
[Symbol.toPrimitive](hint) {
if (hint === 'number') return +this.value;
if (hint === 'string') return this.days[this.value.getDay()];
return this.value.toString();
}
}
console.log(+date); // 1527617604951
console.log(`${date}`); // "Tuesday"
console.log(date+''); // "Tue May 29 2018 21:13:24 GMT+0300 (EEST)"
Symbol.prototype
Definition: Represents the prototype for the Symbol constructor.
Symbol.prototype.isGlobal = function() {
let current = this.valueOf();
let key = Symbol.keyFor(current);
return Symbol.for(key) === current;
}
let globalSymbol = Symbol.for('s1');
let localSymbol = Symbol('s2');
console.log(globalSymbol.isGlobal()); // true
console.log(localSymbol.isGlobal()); // false
By the way if we already made upgrades to
In previous example in order to use our
In the next example we will implement own
Symbol.factory = (function() {
let symbols = [];
return function(...args) {
if(args.length < 1) throw 'Symbol.factory function takes at least one argument';
let symbolObj = symbols.find(sym => sym.hasOwnProperty(name));
if(!symbolObj) {
symbolObj = {[name]: Symbol(name)};
symbols.push(symbolObj);
}
return symbolObj[name];
}
})();
Testing custom
let symbol = Symbol.factory('Hi');
console.log(symbol === Symbol.factory('Hi')); // true
console.log(Symbol.factory('Hi') === Symbol.factory('Hi')); // true
Testing build in
let symbol2 = Symbol.for('Hi');
console.log(symbol2 === Symbol.for('Hi')); // true
console.log(Symbol.for('Hi') === Symbol.for('Hi')); // true
There is a lot awesome thing that you can do with Symbol, for example:
you can implement own built in factory that will manage different repositories of Symbols in order
to implement own custom protocols (something similar is iteration protocols) that can make your life easier.
Symbol.split
Definition: specifies the method that splits a string at the indices that match a regular expression.
/* Helper functions */
function compact(source) {
return source.filter(elem => elem);
};
function pipe(...fns) {
return (...vals) =>
fns.reduce((accumulator, fn) =>
fn(accumulator), fns.shift()(...vals));
}
function upperFirst(str = '') {
let firstChar = str.charAt(0).toUpperCase()
, restOfString = str.substring(1, str.length);
return `${firstChar}${str.substring(1, str.length)}`;
}
function words(strValue) {
let r = (str, ...args) => str.replace(...args);
return pipe(
s => r(s, /[^-a-z-A-Z0-9]+/g, ','),
s => r(s, /([a-z])([A-Z])/g, '$1 $2'),
s => r(s, /([A-Z])([a-z])/g, ' $1$2'),
s => r(s, /\ +/g, ','),
s => s.split(','),
compact
)(strValue);
};
/* Symbol.split example */
class SnakeCase {
[Symbol.split](value) {
return words(value).join('_').toLowerCase();
}
}
class KebabCase {
[Symbol.split](value) {
return words(value).join('-').toLowerCase();
}
}
class CamelCase {
[Symbol.split](value) {
return words(value).reduce((accumulator, value) =>
(accumulator)
? `${accumulator}${upperFirst(value.toLowerCase())}`
: value.toLowerCase(), '');}
}
console.log('JohnDoe Hi_You'.split(new SnakeCase())); // "john_doe_hi_you"
console.log('JohnDoe Hi_You'.split(new KebabCase())); // "john-doe-hi-you"
console.log('JohnDoe Hi_You'.split(new CamelCase())); // "johnDoeHiYou"
console.log('JohnDoe Hi_You'.split(new SnakeCase)); // "john_doe_hi_you"
console.log('JohnDoe Hi_You'.split(new KebabCase)); // "john-doe-hi-you"
console.log('JohnDoe Hi_You'.split(new CamelCase)); // "johnDoeHiYou"
Symbol.hasInstance
Definition: Is used to determine if a constructor object recognizes an object as its instance.
It is just awesome and I will try to explain why.
Unlike TypeScript, JavaScript dosn't have a built in interfaces but we have ability to simulate something similar with
- If it walks like a duck and it quacks like a duck, then it must be a duck.
Let’s consider the following example:
Here we are simulating an interface:
class Flying { // expects fly implementation
static [Symbol.hasInstance](instance) {
return typeof instance.fly === 'function';
}
}
Firstly we create class Bird and Airplane and they are implementing Flying interface. Secondly we are creating Car class that dosn't have that ability.
class Bird {
fly() {
console.log('Bird: I\'m flying!');
}
}
class Airplane {
fly() {
console.log('Airplane: I\'m flying!');
}
}
class Car {
drive() {
console.log('Car: I\'m driving!');
}
}
usage:
let car = new Car();
let airplane = new Airplane();
let bird = new Bird();console.log(bird instanceof Flying); // true
console.log(car instanceof Flying); // false
console.log(airplane instanceof Flying); // true
More advanced usage:
// helper function
function implements(instance, clazz) {
return instance instanceof clazz;
}
// demo function
function flyForMe(instance) {
if(!implements(instance, Flying))
throw new Error('Instance should implement Flying interface');
instance.fly();
}
flyForMe(bird); // "Bird: I'm flying!"
flyForMe(airplane); // "Airplane: I'm flying!"
try {
flyForMe(car);
} catch(err) {
console.log(err.message); // "Instance should implement Flying interface"
}
Symbol.match
Definition: specifies the matching of a regular expression against a string.
let regexp = /hello/;
regexp[Symbol.match] = false;
console.log('/hello/'.startsWith(regexp));
console.log('/hi/'.endsWith(regexp));
Symbol.species
Definition: Specifies a function-valued property that the constructor function uses to create derived objects.
class Array1 extends Array {
static get [Symbol.species]() {
return this;
}
}
class Array2 extends Array {
static get [Symbol.species]() {
return Array;
}
}
console.log(new Array1().map(function(){}) instanceof Array1); // true
console.log(new Array2().map(function(){}) instanceof Array2); // false
console.log(new Array2().map(function(){}) instanceof Array); // true