Previous remarks
ES5 contains five primitive types: Character string,number,Boolean value,null and undefined . ES6 introduces the sixth primitive type, Symbol
The object attribute names of ES5 are strings, which can easily cause property name conflicts. For example, if you use an object provided by someone else and want to add a new method to the object, the name of the new method may conflict with the existing method. If there is a mechanism to ensure that each attribute's name is unique, it fundamentally prevents attribute name conflicts. This is why ES6 introduced Symbol. This article will give a detailed description of the Symbol type in ES6.
Establish
Symbol values are generated by Symbol functions. That is to say, there are two types of attribute names for an object: a string and a Symbol type. Any attribute name that belongs to the Symbol type is unique and guarantees that it does not conflict with other attribute names.
let firstName = Symbol(); let person = {}; person[firstName] = "huochai"; console.log(person[firstName]); // "huochai"
[Note] The new command cannot be used before the Symbol function, otherwise an error will be reported. Because the generated Symbol is a primitive type of value, not an object
//Uncaught TypeError: Symbol is not a constructor let firstName = new Symbol();
The Symbol function accepts an optional parameter that adds a text to describe the Symbol to be created. This description is not available for attribute access, but it is recommended that such a description be added every time a Symbol is created to facilitate reading code and debugging the Symbol program.
let firstName = Symbol("first name"); let person = {}; person[firstName] = "huochai"; console.log("first name" in person); // false console.log(person[firstName]); // "huochai" console.log(firstName); // "Symbol(first name)"
Symbol's description is stored in the internal [[Description]] property, which can be read only when Symbol's toString() method is called. The toString() method of firstName is implicitly called when console.log() is executed, so its description is printed to the log, but it cannot be accessed directly in code [[Description]]
[Type Detection]
Symbol is the original value, and ES6 extends the typeof operator to return "symbol". So you can use typeof to detect whether the variable is symbolic
let symbol = Symbol("test symbol"); console.log(typeof symbol); // "symbol"
Use
Since each Symbol value is not identical, this means that the Symbol value can be used as an identifier for the object's attribute name, which ensures that no attribute with the same name will appear. This is very useful when an object is made up of multiple modules and prevents a key from being accidentally overwritten or overwritten.
Symbol is available for all places where computable attribute names are used
let firstName = Symbol("first name"); // Use a literal property to compute let person = { [firstName]: "huochai" }; // Make this property read-only Object.defineProperty(person, firstName, { writable: false }); let lastName = Symbol("last name"); Object.defineProperties(person, { [lastName]: { value: "match", writable: false } }); console.log(person[firstName]); // "huochai" console.log(person[lastName]); // "match"
In this example, we first create a Symbol attribute firstName for the person object through the computable object literal attribute grammar. The next line of code sets this property to read-only. Subsequently, a read-only Symbol attribute lastName is created through the Object. defineProperties () method, where the object literal property is used again, but it is used as the second parameter of the object. defineProperties () method.
[Note] When the Symbol value is used as the object attribute name, the point operator cannot be used
var mySymbol = Symbol(); var a = {}; a.mySymbol = 'Hello!'; a[mySymbol] // undefined a['mySymbol'] // "Hello!"
As can be seen from the above results, mySymbol in a.mySymbol and a['mySymbol'] is the attribute name of string type, and mySymbol in a[mySymbol] is the attribute name of Symbol type. Although they are all called mySymbol, the values are different.
Although Symbol can be used in all places where computable attribute names are used, in order to share these symbols effectively among different code fragments, a system needs to be established.
Sharing System
Sometimes you want to share the same Symbol in different code. For example, there are two different object types in the application, but you want them to use the same Symbol attribute to represent a unique identifier. Generally speaking, tracking Symbol in a large code base or across files is very difficult and error-prone. For these reasons, ES6 provides a global Symbol registry that can be accessed at any time.
[Symbol.for()]
If you want to create a shared Symbol, use the Symbol.for() method. It accepts only one parameter, the string identifier of the Symbol to be created, which is also used as a description of Symbol.
let uid = Symbol.for("uid"); let object = {}; object[uid] = "12345"; console.log(object[uid]); // "12345" console.log(uid); // "Symbol(uid)"
The Symbol.for() method first searches for the existence of Symbol with the key "uid" in the global Symbol registry. If it exists, return the existing Symbol directly. Otherwise, create a new Symbol and use this key to register in the Symbol global registry, and then return the newly created Symbol.
Subsequently, if the same key is passed in to call Symbol.for(), the same Symbol will be returned.
let uid = Symbol.for("uid"); let object = { [uid]: "12345" }; console.log(object[uid]); // "12345" console.log(uid); // "Symbol(uid)" let uid2 = Symbol.for("uid"); console.log(uid === uid2); // true console.log(object[uid2]); // "12345" console.log(uid2); // "Symbol(uid)
In this example, uid and uid2 contain the same Symbol and can be used interchangeably. The first call to the Symbol.for() method creates the Symbol, and the second call retrieves the Symbol directly from the Symbol global registry.
[Symbol.keyFor()]
There is another feature associated with Symbol sharing: Symbol-related keys can be retrieved in the Symbol global registry using the Symbol.keyFor() method
let uid = Symbol.for("uid"); console.log(Symbol.keyFor(uid)); // "uid" let uid2 = Symbol.for("uid"); console.log(Symbol.keyFor(uid2)); // "uid" let uid3 = Symbol("uid"); console.log(Symbol.keyFor(uid3)); // undefined
Both uid and uid2 return the "uid" key, but in the Symbol Global Registry there is no uid3 Symbol, that is, there is no key associated with it, so ultimately undefined is returned.
[Note] The name of Symbol.for registered for the Symbol value is global and can be retrieved from a different iframe or service worker.
let iframe = document.createElement('iframe'); iframe.src = String(window.location); document.body.appendChild(iframe); console.log(iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo'));// true
In the above code, the Symbol value generated by the iframe window can be obtained on the main page.
Symbol global registry is a shared environment similar to global scope, that is to say, it cannot assume which keys exist in the current environment. When using third-party components, try to use the Symbol key namespace to reduce naming conflicts. For example, jQuery's code can prefix "jquery" to all keys, like "jquery.element" or other similar keys.
Type Conversion
Type conversion is an important language feature in JS, but other types do not have the same value as Symbol logic, so Symbol is not very flexible to use.
The console.log() method is used to output Symbol content, which calls Symbol's String () method and outputs useful information. You can also call the string () method directly like this to get the same content.
let uid = Symbol.for("uid"), desc = String(uid); console.log(desc); // "Symbol(uid)"
The String() function calls the uid.toString() method and returns the contents of the Symbol description of the string type. However, if you try to splice Symbol with a string, the program will throw an error.
let uid = Symbol.for("uid"), desc = uid + ""; // Cause a mistake!
When splicing uid with empty strings, the first step is to force uid to be converted to a string, while Symbol cannot be converted to a string, so the program throws an error directly.
Similarly, Symbol cannot be coerced into a digital type. Mixing Symbol with every mathematical operator causes the program to throw errors
let uid = Symbol.for("uid"), sum = uid / 1; // Cause a mistake!
Trying to divide Symbol by 1, the program throws an error directly. And no matter which mathematical operator is used, it will not work properly.
[Note] Except for Boolean values, because Symbol is similar to non-null values in JS, and its equivalent Boolean value is true
let uid = Symbol.for("uid"); console.log(uid);//'Symbol(uid)' console.log(!uid);//false console.log(Boolean(uid));//true
Attribute Retrieval
Symbol, as the attribute name, will not appear in the for...in, for...of loop, nor will it be returned by Object. getOwnProperty Names (), Object.keys(), JSON.stringify(). Thus, an Object.getOwnpropertySymbols() method was added to ES6 to retrieve the Symbol attribute in the object.
The return value of the Object.getOwnPropertySymbols() method is an array containing all Symbol's own properties
let uid = Symbol.for("uid"); let object = { [uid]: "12345" }; let symbols = Object.getOwnPropertySymbols(object); console.log(symbols.length); // 1 console.log(symbols[0]); // "Symbol(uid)" console.log(object[symbols[0]]); // "12345"
In this code, the object object object object has a Symbol property called uid, and the object.getOwnPropertySymbols() method returns an array containing this property
Another new API -- Reflect.ownKeys() method returns all types of key names, including regular and Symbol key names
let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; console.log(Reflect.ownKeys(obj));// ["enum", "nonEnum", Symbol(my_key)]
Because of the properties with Symbol values as names, they will not be traversed by conventional methods. This feature can be used to define some non-private, but hopefully only internal methods for objects.
var size = Symbol('size'); class Collection { constructor() { this[size] = 0; } add(item) { this[this[size]] = item; this[size]++; } static sizeOf(instance) { return instance[size]; } } var x = new Collection(); Collection.sizeOf(x) // 0 x.add('foo'); Collection.sizeOf(x) // 1 Object.keys(x) // ['0'] Object.getOwnPropertyNames(x) // ['0'] Object.getOwnPropertySymbols(x) // [Symbol(size)]
In the code above, the size attribute of object x is a Symbol value, so neither Object.keys(x) nor Object.getOwnPropertyNames(x) can get it. This creates the effect of a non-private internal approach.
Built-in Symbol
In addition to defining the Symbol values you use, ES6 also provides 11 built-in Symbol values that point to methods used within the language.
1,Symbol.haslnstance
An internal method called when instanceof is executed to detect inheritance information of an object
2,Symbol.isConcatSpreadable
A Boolean value that indicates whether elements in a collection should be regularized to the same level when passing a collection as a parameter to the Array.prototype.concat() method
3,Symbol.iterator
A method of returning iterators
4,Symbol.match
A method called when calling the String.prototype.match() method to compare strings
5,Symbol.replace
A method called when calling the String.prototype.replace() method to replace substrings of strings
6,Symbol.search
A method called when calling the String.prototype.search() method to locate substrings in strings
7,Symbol.species
Constructor for creating derived classes
8,Symbol.split
A method called when calling the String.prototype.split() method to split strings
9,Symbol.toprimitive
A method of returning the original value of an object
10,Symbol.ToStringTag
A string used to create an object description when calling the Object.prototype.toString() method
11,Symbol.unscopables
An object set that defines object attribute names that cannot be referenced by with statements
[Symbol.haslnstance]
Each function has a Symbol.haslnstance method to determine whether an object is an instance of a function. This method is defined in Function.prototype, and all functions inherit the default behavior of instanceof attributes. To ensure that Symbol. haslnstance is not accidentally rewritten, this method is defined as not writable, configurable, and enumerable.
The Symbol.haslnstance method accepts only one parameter, the value to be checked. If the incoming value is an instance of a function, return true
obj instanceof Array;
The above line of code is equivalent to the following line
Array[Symbol.hasInstance](obj);
Essentially, ES6 simply redefines the instanceof operator as a shorthand grammar for this method. Now that method calls are introduced, you can change the way instanceof works at will.
class MyClass { [Symbol.hasInstance](foo) { return foo instanceof Array; } } console.log([1, 2, 3] instanceof new MyClass()); // true
Assuming that an instance-free function is defined, the return value of Symbol.haslnstance can be hard-coded as false
function MyObject() { // ... } Object.defineProperty(MyObject, Symbol.hasInstance, { value: function(v) { return false; } }); let obj = new MyObject(); console.log(obj instanceof MyObject); // false
The above example calls this method to rewrite symbol.haslnstance, defining a new function that always returns false, even if obj is actually an instance of Myobject class, the instanceof operator returns false after calling the object. defineProperty () method.
Of course, based on arbitrary conditions, value checking can also be used to determine whether the detected case is an instance. For example, you can define a number from 1 to 100 as an example of a special number type, with the following code for implementation
function SpecialNumber() { // empty } Object.defineProperty(SpecialNumber, Symbol.hasInstance, { value: function(v) { return (v instanceof Number) && (v >=1 && v <= 100); } }); let two = new Number(2), zero = new Number(0); console.log(two instanceof SpecialNumber); // true console.log(zero instanceof SpecialNumber); // false
In this code, a symbol.hasInstance method is defined, which returns true when the value is an instance of Number and its value is between 1 and 100. So even if there is no direct relationship between the SpecialNumber function and the variable two, the variable two is recognized as an instance of the special Number.
If the Symbol.haslnstance call is to be triggered, the left operand of instanceof must be an object, and if the left operand is non-object, instanceof always returns false.
Of course, you can override the default symbol. haslnstance attribute for all built-in functions, such as Date and Error functions. However, the consequence of this is that the result of the code running becomes unpredictable and may be confusing, so it is not recommended to do so. The best way to do this is to rewrite the Symbol. haslnstance attribute of the function you declare only if necessary.
[Symbol.isConcatSpreadable]
The Symbol.isConcatSpreadable property of the object is a Boolean value indicating whether the object can be expanded when it uses Array.prototype.concat().
let arr1 = ['c', 'd']; ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e'] arr1[Symbol.isConcatSpreadable] // undefined let arr2 = ['c', 'd']; arr2[Symbol.isConcatSpreadable] = false; ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
The code above shows that the default behavior of an array can be expanded. The Symbol.isConcatSpreadable attribute equals undefined or true, and both have this effect.
The class array object can also be expanded, but its Symbol.isConcatSpreadable property defaults to false and must be opened manually.
let obj = {length: 2, 0: 'c', 1: 'd'}; ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e'] obj[Symbol.isConcatSpreadable] = true; ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
For a class, the Symbol.isConcatSpreadable property must be written as an instance property
class A1 extends Array { constructor(args) { super(args); this[Symbol.isConcatSpreadable] = true; } } class A2 extends Array { constructor(args) { super(args); this[Symbol.isConcatSpreadable] = false; } } let a1 = new A1(); a1[0] = 3; a1[1] = 4; let a2 = new A2(); a2[0] = 5; a2[1] = 6; [1, 2].concat(a1).concat(a2) // [1, 2, 3, 4, [5, 6]]
In the above code, class A1 is expandable, class A2 is not expandable, so using concat has different results
[Symbol.species]
The Symbol.species property of the object points to the constructor of the current object. When creating an instance, this method is called by default, even if the function returned by this attribute is used as a constructor to create a new instance object.
class MyArray extends Array { // Override the parent class Array Constructive function static get [Symbol.species]() { return Array; } }
In the code above, the subclass MyArray inherits the parent class Array. When creating an instance object of MyArray, it would have called its own constructor, but since the Symbol.species attribute is defined, it will use the function returned by this attribute to create an instance of MyArray.
This example also shows that the get reader is used to define the Symbol.species attribute. The default Symbol.species attribute is equivalent to the following
static get [Symbol.species]() { return this; }
Here's an example
class MyArray extends Array { static get [Symbol.species]() { return Array; } } var a = new MyArray(1,2,3); var mapped = a.map(x => x * x); mapped instanceof MyArray // false mapped instanceof Array // true
In the above code, because the constructor was replaced with Array. So, the mapped object is not an instance of MyArray, but an instance of Array.
[Symbol.match]
The Symbol.match attribute of the object points to a function. When str.match(myObject) is executed, if the attribute exists, it is called and the return value of the method is returned.
String.prototype.match(regexp) // Equivalent to regexp[Symbol.match](this) class MyMatcher { [Symbol.match](string) { return 'hello world'.indexOf(string); } } 'e'.match(new MyMatcher()) // 1
[Symbol.replace]
The Symbol.replace property of the object, which points to a method, returns the return value of the String.prototype.replace method when the object is called by the String.prototype.replace method
String.prototype.replace(searchValue, replaceValue) // Equivalent to searchValue[Symbol.replace](this, replaceValue)
Here's an example
const x = {}; x[Symbol.replace] = (...s) => console.log(s); 'Hello'.replace(x, 'World') // ["Hello", "World"]
The Symbol.replace method receives two parameters. The first parameter is the object in which the replace method is working. The example above is Hello. The second parameter is the value after the replacement. The example above is World.
[Symbol.search]
The Symbol.search attribute of the object, which points to a method, returns the return value of the String.prototype.search method when the object is called by the String.prototype.search method
String.prototype.search(regexp) // Equivalent to regexp[Symbol.search](this) class MySearch { constructor(value) { this.value = value; } [Symbol.search](string) { return string.indexOf(this.value); } } 'foobar'.search(new MySearch('foo')) // 0
[Symbol.split]
The Symbol.split attribute of the object, which points to a method, returns the return value of the String.prototype.split method when the object is called by the String.prototype.split method
String.prototype.split(separator, limit) // Equivalent to separator[Symbol.split](this, limit)
Here's an example
class MySplitter { constructor(value) { this.value = value; } [Symbol.split](string) { var index = string.indexOf(this.value); if (index === -1) { return string; } return [ string.substr(0, index), string.substr(index + this.value.length) ]; } } 'foobar'.split(new MySplitter('foo'))// ['', 'bar'] 'foobar'.split(new MySplitter('bar'))// ['foo', ''] 'foobar'.split(new MySplitter('baz'))// 'foobar'
Using the Symbol.split method, the above method redefines the behavior of the split method for string objects.
[Symbol.iterator]
Object's Symbol.iterator property, pointing to the default traversal method of the object
var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; }; [...myIterable] // [1, 2, 3]
The Symbol.iterator method is called to return the default traversal of the object when the object is looped for...of.
class Collection { *[Symbol.iterator]() { let i = 0; while(this[i] !== undefined) { yield this[i]; ++i; } } } let myCollection = new Collection(); myCollection[0] = 1; myCollection[1] = 2; for(let value of myCollection) { console.log(value); } // 1 // 2
[Symbol.toPrimitive]
The Symbol.toPrimitive property of the object points to a method. When the object is converted to the value of the original type, this method is called to return the value of the original type corresponding to the object.
When Symbol.toPrimitive is called, it accepts a string parameter representing the current mode of operation. There are three modes.
Number: Conversion to numerical values is required in this case
2. String: This occasion needs to be converted to a string
Default: In this case, it can be converted to a numerical value or to a string.
let obj = { [Symbol.toPrimitive](hint) { switch (hint) { case 'number': return 123; case 'string': return 'str'; case 'default': return 'default'; default: throw new Error(); } } }; 2 * obj // 246 3 + obj // '3default' obj == 'default' // true String(obj) // 'str'
[String.toStringTag]
The Symbol.toStringTag property of the object points to a method. When the Object.prototype.toString method is called on the object, if this property exists, its return value will appear in the string returned by the toString method, representing the type of the object. That is to say, this property can be used to customize the string after the object in [object Object object] or [object Array].
// Example 1 ({[Symbol.toStringTag]: 'Foo'}.toString()) // "[object Foo]" // Example two class Collection { get [Symbol.toStringTag]() { return 'xxx'; } } var x = new Collection(); Object.prototype.toString.call(x) // "[object xxx]"
The Symbol.toStringTag attribute values of the new built-in object in ES6 are as follows:
JSON[Symbol.toStringTag]: 'JSON' Math[Symbol.toStringTag]: 'Math' Module[Symbol.toStringTag]: 'Module' ArrayBuffer.prototype[Symbol.toStringTag]: 'ArrayBuffer' DataView.prototype[Symbol.toStringTag]: 'DataView' Map.prototype[Symbol.toStringTag]: 'Map' Promise.prototype[Symbol.toStringTag]: 'Promise' Set.prototype[Symbol.toStringTag]: 'Set' %TypedArray%.prototype[Symbol.toStringTag]: 'Uint8Array' WeakMap.prototype[Symbol.toStringTag]: 'WeakMap' WeakSet.prototype[Symbol.toStringTag]: 'WeakSet' %MapIteratorPrototype%[Symbol.toStringTag]: 'Map Iterator' %SetIteratorPrototype%[Symbol.toStringTag]: 'Set Iterator' %StringIteratorPrototype%[Symbol.toStringTag]: 'String Iterator' Symbol.prototype[Symbol.toStringTag]: 'Symbol' Generator.prototype[Symbol.toStringTag]: 'Generator' GeneratorFunction.prototype[Symbol.toStringTag]: 'GeneratorFunction'
[Symbol.unscopables]
The Symbol.unscopables attribute of the object points to an object. This object specifies which attributes will be excluded by the with environment when using the with keyword.
Array.prototype[Symbol.unscopables] // { // copyWithin: true, // entries: true, // fill: true, // find: true, // findIndex: true, // includes: true, // keys: true // } Object.keys(Array.prototype[Symbol.unscopables]) // ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys']
The above code shows that the array has seven attributes and will be excluded by the with command.
// No unscopables time class MyClass { foo() { return 1; } } var foo = function () { return 2; }; with (MyClass.prototype) { foo(); // 1 } // Yes unscopables time class MyClass { foo() { return 1; } get [Symbol.unscopables]() { return { foo: true }; } } var foo = function () { return 2; }; with (MyClass.prototype) { foo(); // 2 }
By specifying the Symbol.unscopables attribute, the above code prevents the with grammar block from looking for foo attributes in the current scope, which means that foo will point to variables in the outer scope.