DEV Community

собачья будка
собачья будка

Posted on • Edited on

javascript for impatient programmers. глава 20. символы [перевод]

Вольный перевод главы книги Dr. Axel Rauschmayer "JavaScript for impatient programmers. Symbols"

20 Символы

Символы - это примитивные значения, создаваемые с помощью функции-фабрики Symbol():

const mySymbol = Symbol('mySymbol');
Enter fullscreen mode Exit fullscreen mode

Аргумент опционален и предоставляет описание, которое чаще всего используется для отладки.

С одной стороны, символы похожи на объекты, но с отличием в том, что каждое значение, созданное с помощьюSymbol(), уникально и не может сравниваться по значению:

> Symbol() === Symbol()
false
Enter fullscreen mode Exit fullscreen mode

С другой стороны, символы также ведут себя как примитивы и должны быть категоризированы с помощью typeof:

const sym = Symbol();
assert.equal(typeof sym, 'symbol');
Enter fullscreen mode Exit fullscreen mode

И могут выступать в роли ключей для свойств объектов:

const obj = {

};
Enter fullscreen mode Exit fullscreen mode

20.1 Случаи использования

Чаще всего символы используются в качестве:

  • Значений для констант
  • Уникальных ключей для свойств

20.1.1 Символы: значения для констант

Представим, что вы хотите создать переменных, представляющие цвета red, orange, yellow, green, blue и violet. Это можно сделать с использованием обычных строк:

const COLOR_BLUE = 'Blue';
Enter fullscreen mode Exit fullscreen mode

Плюсом такого подхода будет логгирование такой константы, поскольку оно даст очень полезный выходные данные. Минусом - есть риск ошибочного принятия несвязанного значения цвета, поскольку две строки с одинаковым содержимым рассматриваются как равные:

const MOOD_BLUE = 'Blue';
assert.equal(COLOR_BLUE, MOOD_BLUE);
Enter fullscreen mode Exit fullscreen mode

Эту проблему можно обойти с помощью символов:

const COLOR_BLUE = Symbol('Blue');
const MOOD_BLUE = Symbol('Blue');

assert.notEqual(COLOR_BLUE, MOOD_BLUE);
Enter fullscreen mode Exit fullscreen mode

Давайте создадим функцию с помощью констант с символьным значением:

const COLOR_RED    = Symbol('Red');
const COLOR_ORANGE = Symbol('Orange');
const COLOR_YELLOW = Symbol('Yellow');
const COLOR_GREEN  = Symbol('Green');
const COLOR_BLUE   = Symbol('Blue');
const COLOR_VIOLET = Symbol('Violet');

function getComplement(color) {
  switch (color) {
    case COLOR_RED:
      return COLOR_GREEN;
    case COLOR_ORANGE:
      return COLOR_BLUE;
    case COLOR_YELLOW:
      return COLOR_VIOLET;
    case COLOR_GREEN:
      return COLOR_RED;
    case COLOR_BLUE:
      return COLOR_ORANGE;
    case COLOR_VIOLET:
      return COLOR_YELLOW;
    default:
      throw new Exception('Unknown color: '+color);
  }
}
assert.equal(getComplement(COLOR_YELLOW), COLOR_VIOLET);
Enter fullscreen mode Exit fullscreen mode

20.1.2 Символы: уникальные ключи для свойств

Ключи свойств объектов используются на двух уровнях:

  • Базовый уровень. Ключи отражают проблему, которую решает программа.
  • Meta уровень. Ключи используются сервисами (библиотеками и ECMAScript), работающими с данными и кодом на базовом уровне. Одним из таких ключей является 'toString'.

Код ниже показывает разницу между ними:

const pt = {
  x: 7,
  y: 4,
  toString() {
    return `(${this.x}, ${this.y})`;
  },
};
assert.equal(String(pt), '(7, 4)');
Enter fullscreen mode Exit fullscreen mode

Свойства .x и .y существуют на базовом уровне. Они хранят координаты точки, представленной pt и используются для решения проблемы – вычисления с точками. Метод .toString() существует на meta уровне. Используется JavaScript для конвертации этого объекта в строку.

Свойства meta уровня никогда не должны смешиваться со свойствами базового. Это значит, что их ключи никогда не должны перекрывать друг друга. Этому условию бывает тяжело следовать, если и язык, и библиотеки работают с meta уровнем. Например, сейчас невозможно задавать новым методам meta уровня простые имена, вроде toString, поскольку они могут конфликтовать с уже существующими именами базового уровня. В Python эта проблема была решена путем добавления префикса и суффикса в виде двух подчеркиваний к специальным именам: __init____iter____hash__ и т.д. .Однако, даже с таким решением, библиотеки не могут иметь собственные свойства на meta уровне, потому что есть вероятность конфликта с будущими свойствами языка.

Символы, используемые в качестве ключей для свойств объекта, могут помочь здесь: каждый символ уникален, и никогда не вступит в конфликт с любой другой строкой или символьным ключом.

20.1.2.1 Пример: библиотека с методом на meta уровне

В качестве примера, давайте представим библиотеку, которая по-разному обрабатывает объекты, если в них реализован специальный метод. Определение ключа и реализация такого метода выглядела бы следующим образом:

const specialMethod = Symbol('specialMethod');
const obj = {
  _id: 'kf12oi',
  [specialMethod]() { // (A)
    return this._id;
  }
};
assert.equal(obj[specialMethod](), 'kf12oi');
Enter fullscreen mode Exit fullscreen mode

Квадратные скобки в строке A позволяют нам указать, что метод должен иметь ключ specialMethod. Больше подробностей можно найти в §25.5.2 “Computed property keys”.

20.2 Известные символы

Играющие специальные роли символы, встроенные в ECMAScript, называются известными символами. Примеры включают:

  • Symbol.iterator: делает объект итерируемым. Это ключ метода, который возвращает итератор. Больше подробностей можно найти в §27 “Synchronous iteration”.
  • Symbol.hasInstance: кастомизирует работу instanceof. Если объект реализует метод с таким ключом, то его можно использовать в правой части этого оператора. Пример:

    const PrimitiveNull = {
      [Symbol.hasInstance](x) {
        return x === null;
      }
    };
    assert.equal(null instanceof PrimitiveNull, true);
    
  • Symbol.toStringTag: влияет на дефолтный метод .toString().

    > String({})
    '[object Object]'
    > String({ [Symbol.toStringTag]: 'is no money' })
    '[object is no money]'
    

    Примечание:  .toString(), обычно, лучше переопределять.

20.3 Конвертация символов

Что произойдет, если мы конвертируем символ sym в другой примитивный тип? В таблице приведены ответы.

Convert to Explicit conversion Coercion (implicit conv.)
boolean Boolean(sym) → OK !sym → OK
number Number(sym) → TypeError sym*2 → TypeError
string String(sym) → OK ''+sym → TypeError
sym.toString() → OK ${sym} → TypeError

Ключевая ловушка символов - как часто выбрасываются исключения, когда мы конвертируем их во что-то другое. Что за этим стоит? Во-первых, преобразование к числу вообще не имеет смысла, о чем должно быть предупреждение. Во-вторых, преобразование к строке действительно полезно для оценки выходных данных. Но предупреждать случайные конвертации в строку (которая является совершенно другим типом ключа) тоже имеет смысл:

const obj = {};
const sym = Symbol();
assert.throws(
  () => { obj['__'+sym+'__'] = true },
  { message: 'Cannot convert a Symbol value to a string' });
Enter fullscreen mode Exit fullscreen mode

Недостатком является то, что исключения делают работу с символами более сложной. Вам приходится явно преобразовывать символы при конкатенации строк:

> const mySymbol = Symbol('mySymbol');
> 'Symbol I used: ' + mySymbol
TypeError: Cannot convert a Symbol value to a string
> 'Symbol I used: ' + String(mySymbol)
'Symbol I used: Symbol(mySymbol)'
Enter fullscreen mode Exit fullscreen mode

Top comments (0)