preface

In fact, many of Symbol’s features cannot be simulated… So let’s review the features and pick out the ones that can be implemented… Of course, as you watch, you can also think about whether the feature can be implemented, and if so, how.

review

ES6 introduces a new primitive data type, Symbol, that represents unique values.

1. The value of Symbol is generated by the Symbol function, using typeof, resulting in “Symbol”.

var s = Symbol(a);console.log(typeof s); // "symbol"
Copy the code

2. Do not use the new command before the Symbol function, otherwise an error will be reported. This is because the generated Symbol is a primitive type value, not an object.

3. The result of instanceof is false

var s = Symbol('foo');
console.log(s instanceof Symbol); // false
Copy the code

4. The Symbol function can accept a string as an argument representing a description of the Symbol instance, mainly for display on the console or for easy differentiation when converted to a string.

var s1 = Symbol('foo');
console.log(s1); // Symbol(foo)
Copy the code

5. If the Symbol argument is an object, the object’s toString method is called to convert it to a string, and then a Symbol value is generated.

const obj = {
  toString() {
    return 'abc'; }};const sym = Symbol(obj);
console.log(sym); // Symbol(abc)
Copy the code

6. The Symbol function parameter only represents the description of the current Symbol value, the Symbol function of the same parameter return value is not equal.

// No arguments
var s1 = Symbol(a);var s2 = Symbol(a);console.log(s1 === s2); // false

// With parameters
var s1 = Symbol('foo');
var s2 = Symbol('foo');

console.log(s1 === s2); // false
Copy the code

7. The Symbol value cannot be computed with other types of values and will report an error.

var sym = Symbol('My symbol');

console.log("your symbol is " + sym); // TypeError: can't convert symbol to string
Copy the code

8. The Symbol value can be explicitly converted to a string.

var sym = Symbol('My symbol');

console.log(String(sym)); // 'Symbol(My symbol)'
console.log(sym.toString()); // 'Symbol(My symbol)'
Copy the code

9. The Symbol value can be used as an identifier for property names of objects, ensuring that no properties with the same name appear.

var mySymbol = Symbol(a);// The first way
var a = {};
a[mySymbol] = 'Hello! ';

// The second way
var a = {
  [mySymbol]: 'Hello! '
};

// The third way
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello! ' });

// All the above methods give the same result
console.log(a[mySymbol]); // "Hello!"
Copy the code

10. Symbol as attribute name, this attribute does not appear in for… In, for… Of loop, will not be the Object. The keys (), Object, getOwnPropertyNames (), JSON. The stringify () returns. However, it is not private property, with an Object. GetOwnPropertySymbols method, can get all the Symbol of specified Object attribute names.

var obj = {};
var a = Symbol('a');
var b = Symbol('b');

obj[a] = 'Hello';
obj[b] = 'World';

var objectSymbols = Object.getOwnPropertySymbols(obj);

console.log(objectSymbols);
// [Symbol(a), Symbol(b)]
Copy the code

11. If we want to use the same Symbol value, we can use symbol. for. It takes a string as an argument and searches for a Symbol value with that argument as its name. If so, return the Symbol, otherwise create and return a Symbol with the name of the string.

var s1 = Symbol.for('foo');
var s2 = Symbol.for('foo');

console.log(s1 === s2); // true
Copy the code

The Symbol. KeyFor method returns the key of a registered Symbol type value.

var s1 = Symbol.for("foo");
console.log(Symbol.keyFor(s1)); // "foo"

var s2 = Symbol("foo");
console.log(Symbol.keyFor(s2) ); // undefined
Copy the code

Analysis of the

After looking at the above features, which features do you think can be simulated?

If we want to simulate the implementation of a Symbol, the basic idea is to build a Symbol function and return a unique value.

But before we do that, let’s look at what the specification does when we call Symbol:

Symbol ( [ description ] )

When Symbol is called with optional argument description, the following steps are taken:

  1. If NewTarget is not undefined, throw a TypeError exception.
  2. If description is undefined, var descString be undefined.
  3. Else, var descString be ToString(description).
  4. ReturnIfAbrupt(descString).
  5. Return a new unique Symbol value whose [[Description]] value is descString.

When calling Symbol, the following steps are used:

  1. If new is used, an error is reported
  2. If description is undefined, let descString be undefined
  3. Otherwise let descString be ToString(Description)
  4. If an error is reported, return
  5. Returns a new unique Symbol value whose internal attribute [[Description]] is descString

Since we also need to define a [[Description]] property, we can’t do this by simply returning a value of a primitive type, so we’ll return an object instead.

The first edition

Referring to the specification, we can actually start to write:

/ / the first edition
(function() {
    var root = this;

    var SymbolPolyfill = function Symbol(description) {

        The new command cannot be used before the Symbol function
        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

        If the Symbol argument is an object, the object's toString method is called to convert it to a string, and then a Symbol value is generated.
        var descString = description === undefined ? undefined : String(description)

        var symbol = Object.create(null)

        Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false.enumerable: false.configurable: false}});This method returns a new object. Two objects will not be the same as long as the reference is different
        returnsymbol; } root.SymbolPolyfill = SymbolPolyfill; }) ();Copy the code

Just by referring to the specification, we have implemented points 2, 5, and 6 of the feature.

The second edition

Let’s look at how the other features are implemented:

1. Use typeof, resulting in “symbol”.

With ES5, we cannot modify the result of the Typeof operator, so this cannot be done.

3. The result of instanceof is false

Since this is not implemented through new, the result of instanceof is naturally false.

4. The Symbol function can take a string as an argument that describes the Symbol instance. The main purpose is to make it easier to distinguish when displayed on the console or converted to a string.

When we print a native Symbol value:

console.log(Symbol('1')); // Symbol(1)
Copy the code

However, we can’t implement this method because it returns an object when we simulate it. Of course, if you modify console.log, that’s another story.

8. The Symbol value can be explicitly converted to a string.

var sym = Symbol('My symbol');

console.log(String(sym)); // 'Symbol(My symbol)'
console.log(sym.toString()); // 'Symbol(My symbol)'
Copy the code

When we call the String method, if the object has a toString method, the toString method is called, so we just add a toString method to the returned object to achieve both effects.

/ / the second edition

// Same code as above...

var symbol = Object.create({
    toString: function() {
        return 'Symbol(' + this.__Description__ + ') '; }});// The following code is the same...
Copy the code

The third edition

9. The Symbol value can be used as an identifier for property names of objects, ensuring that no properties with the same name appear.

This is because when we simulate the so-called Symbol value is actually an object with a toString method, when the object is the property name of the object, the implicit conversion is performed, and the toString method we added is still called. For Symbol(‘foo’) and Symbol(‘foo’), both symbols have the same description, but are not equal because they are two objects. However, when used as object property names, both symbols are implicitly converted to the string Symbol(foo). This will cause a property with the same name. Here’s an example:

var a = SymbolPolyfill('foo');
var b = SymbolPolyfill('foo');

console.log(a ===  b); // false

var o = {};
o[a] = 'hello';
o[b] = 'hi';

console.log(o); // {Symbol(foo): 'hi'}
Copy the code

In case we don’t have a property with the same name, which is a very important property after all, we have to change the toString method to return a unique value, so we can’t do point 8, and we need to write another method that generates a unique value, we’ll call it generateName, We save this unique value by adding it to the __Name__ attribute of the returned object.

/ / the third edition
(function() {
    var root = this;

    var generateName = (function(){
        var postfix = 0;
        return function(descString){
            postfix++;
            return '@ @' + descString + '_' + postfix
        }
    })()

    var SymbolPolyfill = function Symbol(description) {

        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

        var descString = description === undefined ? undefined : String(description)

        var symbol = Object.create({
            toString: function() {
                return this.__Name__; }})Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false.enumerable: false.configurable: false
            },
            '__Name__': {
                value: generateName(descString),
                writable: false.enumerable: false.configurable: false}});returnsymbol; } root.SymbolPolyfill = SymbolPolyfill; }) ()Copy the code

Now look at this example:

var a = SymbolPolyfill('foo');
var b = SymbolPolyfill('foo');

console.log(a ===  b); // false

var o = {};
o[a] = 'hello';
o[b] = 'hi';

console.log(o); // Object { "@@foo_1": "hello", "@@foo_2": "hi" }
Copy the code

The fourth edition

Let’s look at the following properties.

** 7. The Symbol value cannot be evaluated with other types of values and will report an error. 六四屠杀

In the case of the + operator, when an implicit conversion is performed, the valueOf method of the object is called first, and if there is no base value, the toString method is called again. Therefore, we consider an error in valueOf, such as:

var symbol = Object.create({
    valueOf: function() {
        throw new Error('Cannot convert a Symbol value')}})console.log('1' + symbol); / / an error
Copy the code

This seems like an easy solution, but what if we call the valueOf method explicitly? For a native Symbol value:

var s1 = Symbol('foo')
console.log(s1.valueOf()); // Symbol(foo)
Copy the code

For the native Symbol, an explicit call to valueOf returns the valueOf the Symbol, and we cannot determine whether the call is explicit or implicit. Therefore, we can only implement half of the call, either implement the implicit call to return the valueOf the Symbol, or implement the explicit call to return the value. Let’s pick the one that doesn’t report errors, which is the latter.

We have to modify the valueOf function:

/ / the fourth edition
// Same code as above...

var symbol = Object.create({
    toString: function() {
        return this.__Name__;
    },
    valueOf: function() {
        return this; }});// The following code is the same...
Copy the code

The fifth edition

10. Symbol as attribute name, this attribute does not appear in for… In, for… Of loop, will not be the Object. The keys (), Object, getOwnPropertyNames (), JSON. The stringify () returns. However, it is not private property, with an Object. GetOwnPropertySymbols method, can get all the Symbol of specified Object attribute names.

Well, it can’t be done.

11. Sometimes, we want to reuse the same Symbol value. The symbol. for method can do this. It takes a string as an argument and searches for a Symbol value with that argument as its name. If so, return the Symbol, otherwise create and return a Symbol with the name of the string.

This implementation is similar to function memory in that we create an object to store the value of Symbol we have created.

The Symbol. KeyFor method returns the key of a registered Symbol type value.

Just iterate through the forMap and look for the key value corresponding to the value.

/ / the fifth edition
// Same code as before...
var SymbolPolyfill = function() {... }var forMap = {};

Object.defineProperties(SymbolPolyfill, {
    'for': {
        value: function(description) {
            var descString = description === undefined ? undefined : String(description)
            return forMap[descString] ? forMap[descString] : forMap[descString] = SymbolPolyfill(descString);
        },
        writable: true.enumerable: false.configurable: true
    },
    'keyFor': {
        value: function(symbol) {
            for (var key in forMap) {
                if (forMap[key] === symbol) returnkey; }},writable: true.enumerable: false.configurable: true}});// The following code is the same...
Copy the code

Complete implementation

To sum up:

Features that cannot be implemented are: 1, 4, 7, 8, and 10

The features that can be implemented are: 2, 3, 5, 6, 9, 11, 12

The final implementation is as follows:

(function() {
    var root = this;

    var generateName = (function(){
        var postfix = 0;
        return function(descString){
            postfix++;
            return '@ @' + descString + '_' + postfix
        }
    })()

    var SymbolPolyfill = function Symbol(description) {

        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

        var descString = description === undefined ? undefined : String(description)

        var symbol = Object.create({
            toString: function() {
                return this.__Name__;
            },
            valueOf: function() {
                return this; }})Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false.enumerable: false.configurable: false
            },
            '__Name__': {
                value: generateName(descString),
                writable: false.enumerable: false.configurable: false}});return symbol;
    }

    var forMap = {};

    Object.defineProperties(SymbolPolyfill, {
        'for': {
            value: function(description) {
                var descString = description === undefined ? undefined : String(description)
                return forMap[descString] ? forMap[descString] : forMap[descString] = SymbolPolyfill(descString);
            },
            writable: true.enumerable: false.configurable: true
        },
        'keyFor': {
            value: function(symbol) {
                for (var key in forMap) {
                    if (forMap[key] === symbol) returnkey; }},writable: true.enumerable: false.configurable: true
        }
    });

    root.SymbolPolyfill = SymbolPolyfill;

})()
Copy the code

ES6 series

ES6 directory address: github.com/mqyqingfeng…

ES6 series is expected to write about 20 chapters, aiming to deepen the understanding of ES6 knowledge points, focusing on the block-level scope, tag template, arrow function, Symbol, Set, Map and Promise simulation implementation, module loading scheme, asynchronous processing and other contents.

If there is any mistake or not precise place, please be sure to give correction, thank you very much. If you like or are inspired by it, welcome star and encourage the author.