Моделирование серии ES6 реализует тип символа

внешний интерфейс GitHub Promise
Моделирование серии ES6 реализует тип символа

предисловие

На самом деле, многие возможности Symbol не могут быть смоделированы и реализованы... Итак, давайте рассмотрим, какие функции есть, а затем выберем те, которые можно реализовать... Конечно, в процессе просмотра вы также можете подумать о том, стоит ли эту функцию можно реализовать, и если да, то как это сделать.

рассмотрение

ES6 представляет новый примитивный тип данных Symbol, который представляет собой уникальное значение.

1. Значение Symbol генерируется функцией Symbol с использованием typeof, результатом является «символ».

var s = Symbol();
console.log(typeof s); // "symbol"

2. Новую команду нельзя использовать перед функцией Symbol, иначе будет сообщено об ошибке. Это связано с тем, что результирующий символ является примитивным значением, а не объектом.

3. Результат instanceof ложный

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

4. Функция Symbol может принимать в качестве параметра строку, представляющую описание экземпляра Symbol, главным образом для того, чтобы его было легче различить при отображении на консоли или при преобразовании в строку.

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

5. Если параметр Symbol является объектом, вызывается метод toString объекта для преобразования его в строку, а затем генерируется значение Symbol.

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

6. Параметры функции Symbol представляют собой только описание текущего значения Symbol, а возвращаемые значения функций Symbol с теми же параметрами не равны.

// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();

console.log(s1 === s2); // false

// 有参数的情况
var s1 = Symbol('foo');
var s2 = Symbol('foo');

console.log(s1 === s2); // false

7. Вычисление значения символов не может быть выполнено с другими типами значений.

var sym = Symbol('My symbol');

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

8. Значения символов могут быть явно преобразованы в строки.

var sym = Symbol('My symbol');

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

9. Значение Symbol можно использовать в качестве идентификатора имени свойства объекта, чтобы гарантировать, что свойство с таким же именем не появится.

var mySymbol = Symbol();

// 第一种写法
var a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
var a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
console.log(a[mySymbol]); // "Hello!"

10. В качестве имени свойства Symbol не будет появляться в циклах for...in, for...of и не будет возвращаться Object.keys(), Object.getOwnPropertyNames(), JSON.stringify(). Однако это тоже не частное свойство, существует метод Object.getOwnPropertySymbols, который может получить все имена свойств Symbol указанного объекта.

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)]

11. Если мы хотим использовать одно и то же значение Symbol, мы можем использовать Symbol.for. Он принимает строку в качестве параметра и ищет значение символа с этим параметром в качестве имени. Если есть, верните значение Symbol, в противном случае создайте и верните значение Symbol со строкой в ​​качестве имени.

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

console.log(s1 === s2); // true

12. Метод Symbol.keyFor возвращает ключ зарегистрированного значения типа Symbol.

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

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

анализировать

После прочтения описанных выше функций, какие характеристики, по вашему мнению, можно смоделировать?

Если мы хотим имитировать символ, основная идея состоит в том, чтобы создать функцию символа и напрямую возвращать уникальное значение.

Но перед этим давайте посмотримТехнические характеристикиЧто именно он делает при вызове 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.

При вызове Symbol выполняются следующие шаги:

  1. Если вы используете new , вы получите сообщение об ошибке
  2. Если описание не определено, пусть descString будет неопределенным
  3. иначе пусть descString будет ToString(описание)
  4. Если сообщается об ошибке, верните
  5. Возвращает новое уникальное значение символа, внутреннее свойство [[Description]] которого равно descString.

Учитывая, что нам также необходимо определить свойство [[Description]], сделать это, напрямую возвращая значение примитивного типа, невозможно, поэтому в итоге мы возвращаем объект.

первое издание

Ссылаясь на спецификацию, мы можем начать писать:

// 第一版
(function() {
    var root = this;

    var SymbolPolyfill = function Symbol(description) {

        // 实现特性第 2 点:Symbol 函数前不能使用 new 命令
        if (this instanceof SymbolPolyfill) throw new TypeError('Symbol is not a constructor');

        // 实现特性第 5 点:如果 Symbol 的参数是一个对象,就会调用该对象的 toString 方法,将其转为字符串,然后才生成一个 Symbol 值。
        var descString = description === undefined ? undefined : String(description)

        var symbol = Object.create(null)

        Object.defineProperties(symbol, {
            '__Description__': {
                value: descString,
                writable: false,
                enumerable: false,
                configurable: false
            }
        });

        // 实现特性第 6 点,因为调用该方法,返回的是一个新对象,两个对象之间,只要引用不同,就不会相同
        return symbol;
    }

    root.SymbolPolyfill = SymbolPolyfill;
})();

Просто обратитесь к спецификации, мы достигли первых 2,5,6 точечных характеристик.

второе издание

Посмотрим, как реализованы остальные функции:

1. При использовании typeof результатом является «символ».

В ES5 мы не можем изменить результат оператора typeof, поэтому этого сделать нельзя.

3. Результат instanceof ложный

Поскольку это не реализовано с помощью new, результат instanceof, естественно, ложный.

4. Функция Symbol может принимать в качестве параметра строку, представляющую описание экземпляра Symbol. В основном для того, чтобы его было легче отличить при отображении на консоли или при преобразовании в строку.

Когда мы печатаем собственное значение Symbol:

console.log(Symbol('1')); // Symbol(1)

Однако, когда мы имитируем реализацию, возвращаемый объект является объектом, поэтому этого также невозможно добиться, конечно, если вы модифицируете метод console.log, другое дело.

8. Значения символов могут быть явно преобразованы в строки.

var sym = Symbol('My symbol');

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

При вызове метода String, если у объекта есть метод toString, будет вызван метод toString, поэтому нам нужно только добавить метод toString к возвращаемому объекту для достижения этих двух эффектов.

// 第二版

// 前面面代码相同 ……

var symbol = Object.create({
    toString: function() {
        return 'Symbol(' + this.__Description__ + ')';
    },
});

// 后面代码相同 ……

Третье издание

9. Значение Symbol можно использовать в качестве идентификатора имени свойства объекта, чтобы гарантировать, что свойство с таким же именем не появится.

Вроде ничего, но на самом деле это противоречит пункту 8. Это связано с тем, что когда так называемое значение Symbol, которое мы моделируем, на самом деле является объектом с методом toString, когда объект используется в качестве имени атрибута объекта, это будет неявно. Для преобразования типа будет по-прежнему вызываться добавленный нами метод toString. Для двух значений Symbol, Symbol('foo') и Symbol('foo'), хотя описания одинаковы, они не равны потому что это два объекта, а как Когда имя свойства объекта неявно преобразуется вSymbol(foo)String, на этот раз будет создан атрибут с таким же именем. Например:

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'}

Для того, чтобы свойства с одинаковыми именами не появлялись, ведь это очень важная фича, нам приходится модифицировать метод toString, чтобы он возвращал уникальное значение, поэтому пункт 8 реализовать нельзя, а еще надо написать другая функция. Метод для создания уникального значения называется generateName, и мы добавляем уникальное значение в атрибут __Name__ возвращаемого объекта и сохраняем его.

// 第三版
(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
            }
        });

        return symbol;
    }


    root.SymbolPolyfill = SymbolPolyfill;

})()

Теперь посмотрите на этот пример:

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" }

Четвертое издание

Давайте посмотрим на следующие функции.

** 7.Значения символов нельзя оперировать с другими типами значений, и будет сообщено об ошибке. **

к+Возьмем оператор в качестве примера.При выполнении неявного преобразования типов сначала будет вызван метод valueOf объекта.Если базовое значение не будет возвращено, будет вызван метод toString, поэтому мы считаем сообщение об ошибке в методе valueOf, Например:

var symbol = Object.create({
    valueOf: function() {
        throw new Error('Cannot convert a Symbol value')
    }
})

console.log('1' + symbol); // 报错

Похоже на простое решение этой проблемы, но что, если мы явно вызовем метод valueOf? Для собственного значения символа:

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

Да, для нативного Symbol явный вызов метода valueOf будет напрямую возвращать значение Symbol, и мы не можем судить, является ли это явным или неявным вызовом, поэтому мы можем реализовать только половину этого, иначе неявный вызов сообщит об ошибке. error. , либо реализуем явный вызов для возврата значения, то... выберем тот, который не сообщает об ошибке, то есть последний.

В крайнем случае нам пришлось изменить функцию valueOf:

// 第四版
// 前面面代码相同 ……

var symbol = Object.create({
    toString: function() {
        return this.__Name__;
    },
    valueOf: function() {
        return this;
    }
});
// 后面代码相同 ……

Пятое издание

10. В качестве имени свойства Symbol не будет появляться в циклах for...in, for...of и не будет возвращаться Object.keys(), Object.getOwnPropertyNames(), JSON.stringify(). Однако это тоже не частное свойство, существует метод Object.getOwnPropertySymbols, который может получить все имена свойств Symbol указанного объекта.

Ну, это невозможно сделать.

11. Иногда нам нужно повторно использовать одно и то же значение Symbol, метод Symbol.for может это сделать. Он принимает строку в качестве параметра и ищет значение символа с этим параметром в качестве имени. Если есть, верните значение Symbol, в противном случае создайте и верните значение Symbol со строкой в ​​качестве имени.

Эта реализация аналогична памяти функций, мы создаем объект для хранения созданного значения Symbol.

12. Метод Symbol.keyFor возвращает ключ зарегистрированного значения типа Symbol.

Перейдите forMap, чтобы найти значение ключа, соответствующее значению.

// 第五版
// 前面代码相同 ……
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) return key;
            }
        },
        writable: true,
        enumerable: false,
        configurable: true
    }
});
// 后面代码相同 ……

полная реализация

В итоге:

Не удалось выполнить: 1, 4, 7, 8, 10

Это может быть достигнуто: 2, 3, 5, 6, 9, 11, 12

Окончательная реализация выглядит следующим образом:

(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) return key;
                }
            },
            writable: true,
            enumerable: false,
            configurable: true
        }
    });

    root.SymbolPolyfill = SymbolPolyfill;

})()

серия ES6

Адрес каталога серии ES6:GitHub.com/ в настоящее время имеет бриз…

Ожидается, что в серии ES6 будет написано около 20 статей, направленных на углубление понимания некоторых точек знаний ES6, с акцентом на область действия на уровне блоков, шаблоны меток, функции стрелок, реализацию моделирования символов, наборов, карт и обещаний, схему загрузки модулей, асинхронность. обработка и т.п. содержание.

Если есть какие-либо ошибки или неточности, пожалуйста, поправьте меня, большое спасибо. Если вам нравится или у вас есть вдохновение, добро пожаловать в звезду, что также является поощрением для автора.