Тип данных Symbol

Новый примитивный тип данных Symbol служит для создания уникальных идентификаторов.

Мы вначале рассмотрим объявление и особенности символов, а затем – их использование.

Объявление

Синтаксис:

let sym = Symbol();

Обратим внимание, не new Symbol, а просто Symbol, так как это – примитив.

У символов есть и соответствующий typeof:

'use strict';

let sym = Symbol();
alert( typeof sym ); // symbol

Каждый символ – уникален. У функции Symbol есть необязательный аргумент «имя символа». Его можно использовать для описания символа, в целях отладки:

'use strict';

let sym = Symbol("name");
alert( sym.toString() ); // Symbol(name)

…Но при этом, если у двух символов одинаковое имя, то это не значит, что они равны:

alert( Symbol("name") == Symbol("name") ); // false

Если хочется из разных частей программы использовать именно одинаковый символ, то можно передавать между ними объект символа или же – использовать «глобальные символы» и «реестр глобальных символов», которые мы рассмотрим далее.

Глобальные символы

Существует «глобальный реестр» символов, который позволяет, при необходимости, иметь общие «глобальные» символы, которые можно получить из реестра по имени.

Для чтения (или создания, при отсутствии) «глобального» символа служит вызов Symbol.for(имя).

Например:

'use strict';

// создание символа в реестре
let name = Symbol.for("name");

// символ уже есть, чтение из реестра
alert( Symbol.for("name") == name ); // true

Таким образом, можно из разных частей программы, обратившись к реестру, получить единый глобальный символ с именем "name".

На заметку:

В некоторых языках программирования, например Ruby, имя однозначно идентифицирует символ. В JavaScript, как мы видим, это верно для глобальных символов.

У вызова Symbol.for, который возвращает символ по имени, есть обратный вызов – Symbol.keyFor(sym). Он позволяет получить по глобальному символу его имя:

'use strict';

// создание символа в реестре
let name = Symbol.for("name");

// получение имени символа
alert( Symbol.keyFor(name) ); // name
Symbol.keyFor возвращает undefined, если символ не глобальный

Заметим, что Symbol.keyFor работает только для глобальных символов, для остальных будет возвращено undefined:

'use strict';

alert( Symbol.keyFor(Symbol.for("name")) ); // name, глобальный
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, обычный символ

Таким образом, имя символа, если этот символ не глобальный, не имеет особого применения, оно полезно лишь в целях вывода и отладки.

Использование символов

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

'use strict';

let isAdmin = Symbol("isAdmin");

let user = {
  name: "Вася",
  [isAdmin]: true
};

alert(user[isAdmin]); // true

Особенность символов в том, что если в объект записать свойство-символ, то оно не участвует в итерации:

'use strict';

let user = {
  name: "Вася",
  age: 30,
  [Symbol.for("isAdmin")]: true
};

// в цикле for..in также не будет символа
alert( Object.keys(user) ); // name, age

// доступ к свойству через глобальный символ — работает
alert( user[Symbol.for("isAdmin")] );

Кроме того, свойство-символ недоступно, если обратиться к его названию: user.isAdmin не существует.

Зачем всё это, почему просто не использовать строки?

Резонный вопрос. На ум могут прийти соображения производительности, так как символы – это по сути специальные идентификаторы, они компактнее, чем строка. Но при современных оптимизациях объектов это редко имеет значение.

Самое широкое применение символов предусмотрено внутри самого стандарта JavaScript. В современном стандарте есть много системных символов. Их список есть в спецификации, в таблице Well-known Symbols. В спецификации для краткости символы принято обозначать как „@@имя“, например @@iterator, но доступны они как свойства Symbol.

Например:

  • Symbol.toPrimitive – идентификатор для свойства, задающего функцию преобразования объекта в примитив.
  • Symbol.iterator – идентификатор для свойства, задающего функцию итерации по объекту.
  • …и т.п.

Мы легко поймём смысл введения нового типа «символ», если поставим себя на место создателей языка JavaScript.

Допустим, в новом стандарте нам надо добавить к объекту «особый» функционал, например, функцию, которая задаёт преобразование объекта к примитиву. Как obj.toString, но для преобразования в примитивы.

Мы ведь не можем просто сказать, что «свойство obj.toPrimitive теперь будет задавать преобразование к примитиву и автоматически вызываться в таких-то ситуациях». Это опасно. Мы не можем так просто взять и придать особый смысл свойству. Мало ли, вполне возможно, что свойство с таким именем уже используется в существующем коде, и если сделать его особым, то он сломается.

Нельзя просто взять и зарезервировать какие-то свойства существующих объектов для нового функционала.

Поэтому ввели целый тип «символы». Их можно использовать для задания таких свойств, так как они:

  • а) уникальны,
  • б) не участвуют в циклах,
  • в) заведомо не сломают старый код, который о них слыхом не слыхивал.

Продемонстрируем отсутствие конфликта для нового системного свойства Symbol.iterator:

'use strict';

let obj = {
  iterator: 1,
  [Symbol.iterator]() {}
}

alert(obj.iterator); // 1
alert(obj[Symbol.iterator]) // function, символ не конфликтует

Выше мы использовали системный символ Symbol.iterator, поскольку он один из самых широко поддерживаемых. Мы подробно разберём его смысл в главе про итераторы, пока же – это просто пример символа.

Чтобы получить все символы объекта, есть особый вызов Object.getOwnPropertySymbols.

Эта функция возвращает все символы в объекте (и только их). Заметим, что старая функция getOwnPropertyNames символы не возвращает, что опять же гарантирует отсутствие конфликтов со старым кодом.

'use strict';

let obj = {
  iterator: 1,
  [Symbol.iterator]: function() {}
}

// один символ в объекте
alert( Object.getOwnPropertySymbols(obj)[0].toString() ); // Symbol(Symbol.iterator)

// и одно обычное свойство
alert( Object.getOwnPropertyNames(obj) ); // iterator

Итого

  • Символы – новый примитивный тип, предназначенный для уникальных идентификаторов.
  • Все символы уникальны. Символы с одинаковым именем не равны друг другу.
  • Существует глобальный реестр символов, доступных через метод Symbol.for(name). Для глобального символа можно получить имя вызовом и Symbol.keyFor(sym).

Основная область использования символов – это системные свойства объектов, которые задают разные аспекты их поведения. Поддержка у них пока небольшая, но она растёт. Системные символы позволяют разработчикам стандарта добавлять новые «особые» свойства объектов, при этом не резервируя соответствующие строковые значения.

Системные символы доступны как свойства функции Symbol, например Symbol.iterator.

Мы можем создавать и свои символы, использовать их в объектах. Записывать их как свойства Symbol, разумеется, нельзя. Если нужен глобально доступный символ, то используется Symbol.for(имя).

Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.