JavaScript ES6 Symbol ultimate tutorial

Posted by hax on Sun, 10 Oct 2021 13:38:03 +0200

Today, let's learn the new primitive type Symbol of JavaScript. We can use Symbol to create a unique value as an object attribute or value, or modify the internal logic of JS language through Symbol's well-known.

Create Symbol

ES6 adds symbol as the original data type, and other original data types, such as number, boolean, null, undefined and string. The symbol type has no text form.

To create a symbol, we use the global function Symbol()

let s = Symbol('foo');

Each call to the Symbol() function creates a new unique value

console.log(Symbol() === Symbol()); // false

The Symbol() function accepts an optional parameter as a description, which makes the Symbol more semantic.

The following two symbol s are created: firstName and lastName

let firstName = Symbol('first name'),
    lastName = Symbol('last name');

When we use console.log() to print symbol, we will implicitly call the toString() method of symbol.

console.log(firstName); // Symbol(first name)
console.log(lastName); // Symbol(last name)

Since symbol is the original value, we can use typeof to check its type. Similarly, ES6 expands the typeof keyword and returns symbol when encountering symbol type

console.log(typeof firstName); 
// symbol

Because it is an original type, you cannot use new to create it

let s = new Symbol(); // error

Share symbol

To create a shared symbol, use the Symbol.for() function instead of Symbol().

Symbol.for() also accepts an optional parameter as a description

let ssn = Symbol.for('ssn');

Symbol.for() will first find out whether there is a symbol of the created ssn in the global. If so, it will return the created symbol. If not, it will create a new symbol.

Next, let's create the same symbol and see if it's not the same symbol

let nz = Symbol.for('ssn');
console.log(ssn === nz); // true

Because the symbol of ssn has been created above, the symbol of nz variable will be the same as that created above.

If you want to get the key of symbol, use the Symbol.keyFor() method

console.log(Symbol.keyFor(nz)); // 'ssn'

Note that if the symbol is created through Symbol(), using Symbol.keyFor() returns undefined

let systemID = Symbol('sys');
console.log(Symbol.keyFor(systemID)); // undefined

What's the use of symbols

1) Use Symbol as unique value

We often use strings or numbers to represent some states in the code, and we often face the problem of lack of semantics or repeated definition. At this time, using symbols is the best choice. Each newly created Symbol is unique and will not produce repetition, and we can pass in corresponding descriptions to symbols.

Looking at the following example, we use symbols to express several statuses of orders instead of strings and numbers

let statuses = {
    OPEN: Symbol('Order placed'),
    IN_PROGRESS: Symbol('In distribution'),
    COMPLETED: Symbol('Order completion'),
    CANCELED: Symbol('Order cancellation')
};

// Complete order
task.setStatus(statuses.COMPLETED);

2) Use symbol as object attribute

Use Symbol as the attribute name

let status = Symbol('status');

let task = {
    [status]: statuses.OPEN,
    description: 'study ES6 Symbol'
};

console.log(task);

Use Object.keys() to get all enumerable properties of the object

console.log(Object.keys(task));
// ["description"]

Use Object.getOwnPropertyNames() to get all properties, whether enumerable or not

console.log(Object.getOwnPropertyNames(task));
// ["description"]

To get the Symbol property in the object, you need to use the Object.getOwnPropertySymbols() method added in ES6

console.log(Object.getOwnPropertySymbols(task));
//[Symbol(status)]

Well-known symbol

ES6 defines Symbol related attributes on the prototype chain to expose more language internal logic. Well known Symbol defines some functions for standard objects that were previously only visible within the language.

Symbol.hasInstance

Symbol.hasInstance is a symbol that changes the default behavior of the instanceof operator. We usually use instanceof in this way

obj instanceof type;

Then JavaScript will execute the symbol. Hastance method, as follows

type[Symbol.hasInstance](obj);

It will call the Symbol.hasInstance static method of type with obj as the parameter

class Stack {
}
console.log([] instanceof Stack);
// false

[] array is not an instance created by Stack class, so false is returned.

Assuming that the [] array is an instance created by the Stack class and returns true, we can override the method of Symbol.hasInstance

class Stack {
    static [Symbol.hasInstance](obj) {
        return Array.isArray(obj);
    }
}
console.log([] instanceof Stack);
// true

Symbol.iterator

Symbol.iterator specifies whether the function returns an iterator of the object.

Objects with the Symbol.iterator attribute are called iteratable objects.

In ES6, Array, Set, Map, and string are all iteratable objects.

ES6 provides a for... of loop, which can be used on iteratable objects.

var numbers = [1, 2, 3];
for (let num of numbers) {
    console.log(num);
}

// 1
// 2
// 3

In the back, the JavaScript engine first calls the Symbol.iterator method of the numbers array to obtain the iterator object, and then it calls the iterator.next() method and copies the value attribute of the iterator object into the num variable. After three iterations, the done attribute of the object is true and is pushed out circularly.

We can get the iterator object of the array through Symbol.iterator.

var iterator = numbers[Symbol.iterator]();

console.log(iterator.next()); // Object {value: 1, done: false}
console.log(iterator.next()); // Object {value: 2, done: false}
console.log(iterator.next()); // Object {value: 3, done: false}
console.log(iterator.next()); // Object {value: undefined, done: true}

By default, a self-defined collection cannot be iterated, but we can make it iteratable with Symbol.iterator

class List {
  constructor() {
    this.elements = [];
  }

  add(element) {
    this.elements.push(element);
    return this;
  }

  *[Symbol.iterator]() {
    for (let element of this.elements) {
      yield element;
    }
  }
}

let chars = new List();
chars.add('A')
     .add('B')
     .add('C');

// Iteration is implemented using Symbol.iterator
for (let c of chars) {
  console.log(c);
}

// A
// B
// C

Symbol.isConcatSpreadable

We can use the concat() method to combine two arrays

let odd  = [1, 3],
    even = [2, 4];
let all = odd.concat(even);
console.log(all); // [1, 3, 2, 4]

We can also use concat() to pass in a single element instead of an array

let extras = all.concat(5);
console.log(extras); // [1, 3, 2, 4, 5]

In the above example, when we pass an array to the concat() method, the concat() method will expand the array into a single element. However, it will treat a single original parameter in a different way, and we can't change this behavior until ES6.

let list = {
    0: 'JavaScript',
    1: 'Symbol',
    length: 2
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", Object]

The list object is merged into the ['Learning '] array, but the elements in the list object are not merged into the array.

To add the elements in the list object to the array separately during concat(), we need to add the symbol.isconcapspreable attribute to the list object, as follows

let list = {
    0: 'JavaScript',
    1: 'Symbol',
    length: 2,
    [Symbol.isConcatSpreadable]: true
};
let message = ['Learning'].concat(list);
console.log(message); // ["Learning", "JavaScript", "Symbol"]

If Symbol.isConcatSpreadable is set to false, concat() will merge the entire list object into the array.

Symbol.toPrimitive

The Symbol.toPrimitive method determines the behavior when an object is converted to its original value.

The JavaScript engine defines the Symbol.toPrimitive method on the prototype of each type value.

The Symbol.toPrimitive method accepts a hint parameter, which will be the following three values: string, number and default. The hint parameter is used to specify the type of return value. The hint parameter is populated by the JavaScript engine based on the context of the object being used.

function Money(amount, currency) {
    this.amount = amount;
    this.currency = currency;
}

Money.prototype[Symbol.toPrimitive] = function(hint) {
    var result;
    switch (hint) {
        case 'string':
            result = this.amount + this.currency;
            break;
        case 'number':
            result = this.amount;
            break;
        case 'default':
            result = this.amount + this.currency;
            break;
    }
    return result;
}

var price = new Money(10000, 'RMB');

console.log('I have ' + price); // Price is 799USD
console.log(+price + 1); // 800
console.log(String(price)); // 799USD

other

  • Symbol.match(regex): a method called when calling the String.prototype.match() method to compare strings.

  • Symbol.replace(regex, replacement): a method called when calling the String.prototype.replace() method, which is used to replace the substring of the string.

  • Symbol.search(regex): a method called when calling the String.prototype.search() method to locate a substring in a string.

  • Symbol. Categories (regex): constructor used to create derived objects.

  • Symbol.split: a method called when calling the String.prototype.split() method, which is used to split the string.

  • Symbol.toStringTag: a string used when calling the String.prototype.toString() method to create an object description.

  • Symbol.unscopables: a collection of objects that define object attribute names that cannot be referenced by the with statement.

summary

Today, we learned all the usage methods of Symbol and some commonly used methods. I hope it can help you.

If this article is helpful, wechat search [Xiaoshuai's Programming Notes] and let us make progress every day

Topics: Javascript Front-end