Symbol

Symbol — это встроенный объект, конструктор которого возвращает значение примитивного типа symbol. Такие значения называют символьными значениями (Symbol value) или просто символами (Symbol), их основная особенность в том, что они гарантируют уникальность. Символы часто используются в качестве уникальных ключей объекта. Они не пересекаются с ключами, которые могут быть добавлены другим кодом, а также скрыты от доступа из другого кода. Это обеспечивает возможность слабой инкапсуляции или слабую форму сокрытия информации.

Каждый вызов Symbol() гарантированно возвращает уникальный символ. Каждый вызов Symbol.for("key") всегда будет возвращать один и тот же символ для указанного значения "key". При вызове Symbol.for("key") осуществляется поиск в глобальном реестре символов. Если символ найден, то он возвращается, в противном случае создаётся новый символ, добавляется в глобальный реестр под заданным ключом и возвращается.

Описание

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

js
const sym1 = Symbol();
const sym2 = Symbol("foo");
const sym3 = Symbol("foo");

Код выше создаёт три новых символа. Обратите внимание, что Symbol("foo") не выполняет приведение строки "foo" к символу. Это выражение создаёт каждый раз новый символ:

js
Symbol("foo") === Symbol("foo"); // false

Код ниже с оператором new вызовет исключение TypeError:

js
const sym = new Symbol(); // TypeError

Это удерживает разработчиков от создания явного объекта-обёртки Symbol вместо нового символьного значения, но может быть неожиданным, так как создание явных объектов-обёрток для примитивных типов доступно (например, new Boolean, new String, new Number).

Если действительно необходимо обернуть символ в объект, можно использовать функцию Object():

js
const sym = Symbol("foo");
typeof sym; // "symbol"
const symObj = Object(sym);
typeof symObj; // "object"

Поскольку символы — единственный примитивный тип данных, который имеет ссылочную идентичность (то есть нельзя создать один и тот же символ дважды), они в некотором смысле ведут себя как объекты. Например, они подлежат возможности сборки мусора и поэтому могут храниться в WeakMap, WeakSet, WeakRef и FinalizationRegistry.

Общие символы в глобальном реестре символов

Приведённый выше синтаксис использования функции Symbol() создаёт символ, значение которого будет уникальным на протяжении всего времени существования программы. Чтобы создавать символы, доступные в разных файлах и даже областях видимости, можно использовать методы Symbol.for() и Symbol.keyFor() для установки и получения символов из глобального реестра символов.

Обратите внимание, что «глобальный реестр символов» — это всего лишь концепция. Реализация может не соответствовать какой-либо внутренней структуре данных в движке JavaScript, и даже если такой реестр существует, его содержимое недоступно для кода JavaScript, кроме как через методы for() и keyFor().

Метод Symbol.for(tokenString) принимает строковый ключ и возвращает символьное значение из реестра, а метод Symbol.keyFor(symbolValue) принимает символьное значение и возвращает соответствующий ему строковый ключ. Каждый из них является обратным другому, поэтому следующее выражение истинно:

js
Symbol.keyFor(Symbol.for("tokenString")) === "tokenString"; // true

Поскольку зарегистрированные символы могут быть созданы в произвольном месте, они ведут себя почти так же, как строки, которые они оборачивают, их уникальность не гарантируется они не подлежат возможности сборке мусора. Поэтому зарегистрированные символы нельзя использовать в WeakMap, WeakSet, WeakRef и FinalizationRegistry.

Общеизвестные символы

Все статические свойства конструктора Symbol сами являются символами, значение которых одинаковы во всех областях видимости. Они называются общеизвестными символами и служат «протоколами» для некоторых встроенных операций JavaScript, позволяя пользователям настраивать поведение языка. Например, если функция-конструктор имеет метод с именем Symbol.hasInstance, то его поведение будет реализовано с помощью оператора instanceof.

До появления общеизвестных символов в JavaScript использовались обычные свойства для реализации определённых встроенных операций. Например, функция JSON.stringify попытается вызвать метод объекта toJSON(), а функция String вызовет методы объекта toString() и valueOf(). Однако по мере того, как в язык добавляется всё больше новых операций, назначение каждой операции «магического свойства» может нарушить обратную совместимость и затруднить понимание поведения языка. Общеизвестные символы позволяют настройкам быть «невидимыми» для обычного кода, который как правило обращается только к строковым свойствам.

В MDN и других источниках значения общеизвестных символов обозначаются с помощью префикса @@. Например, Symbol.hasInstance записывается как @@hasInstance. Это связано с тем, что символы не имеют фактических литеральных форматов, а использование Symbol.hasInstance не отражает возможность использования других псевдонимов для ссылки на тот же символ. Это похоже на разницу между Function.name и Function.

Общеизвестные символы не подлежат возможности сборки мусора, поскольку они входят в фиксированный набор и уникальны на протяжении всего времени существования программы, подобно внутренним объектам, таким как Array.prototype. Поэтому их также можно использовать в WeakMap, WeakSet, WeakRef и FinalizationRegistry.

Поиск символьных свойств у объектов

Метод Object.getOwnPropertySymbols() возвращает массив символов и позволяет получить символьные свойства конкретного объекта. Следует отметить, что при инициализации у объектов нет своих символьных свойств, поэтому этот массив будет пуст, пока у объекта не будут установлены символьные свойства.

Конструктор

Symbol()

Создаёт новый объект Symbol. Не является конструктором в привычном понимании, потому что может быть вызван только как обычная функция, но не new Symbol().

Статические свойства

Статические свойства являются общеизвестными символами. Для описания таких символов мы используем выражения подобные «Symbol.hasInstance — это метод, определяющий…», но следует иметь в виду, что это относится к семантике метода объекта, имеющего этот символ в качестве имени метода (поскольку общеизвестные символы действуют как «протоколы»), а не описывает значение самого символа.

Symbol.asyncIterator

Метод возвращает используемый по умолчанию AsyncIterator объекта. Используется в for await...of.

Symbol.hasInstance

Метод определяет, распознаёт ли конструктор объект как свой экземпляр. Используется в instanceof.

Symbol.isConcatSpreadable

Логическое значение, указывающее, может ли объект быть сведён к элементам массива. Используется в Array.prototype.concat().

Symbol.iterator

Метод возвращает используемый по умолчанию итератор объекта. Используется в for...of.

Symbol.match

Метод для сопоставления со строкой, также используется для определения того, можно ли использовать объект в качестве регулярного выражения. Используется в String.prototype.match().

Symbol.matchAll

Метод возвращает итератор, который определяет совпадения строки с регулярным выражением. Используется в String.prototype.matchAll().

Symbol.replace

Метода заменяет совпадающие подстроки в строке. Используется в String.prototype.replace().

Symbol.search

Метод, возвращающий индекс внутри строки, соответствующий регулярному выражению. Используется в String.prototype.search().

Symbol.species

Функция-конструктор, используемая для создания производных объектов.

Symbol.split

Метод, который разбивает строку по индексам, соответствующим регулярному выражению. Используется в String.prototype.split().

Symbol.toPrimitive

Метод преобразует объект в примитивное значение.

Symbol.toStringTag

Строковое значение, используемое для описания объекта по умолчанию. Используется в Object.prototype.toString().

Symbol.unscopables

Значение объекта, имена собственных и унаследованных свойств которого исключены из привязок with связанного объекта.

Статические методы

Symbol.for()

Ищет существующие зарегистрированные символы в глобальном реестре символов с указанным ключом и возвращает его, если он найден. В противном случае будет создан новый символ и зарегистрирован с указанным ключом.

Symbol.keyFor()

Извлекает общий ключ символа из глобального реестра символов для данного символа.

Свойства экземпляра

Эти свойства определены в Symbol.prototype и есть у всех экземпляров Symbol.

Symbol.prototype.constructor

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

Symbol.prototype.description

Доступная только для чтения строка с описанием символа.

Symbol.prototype[@@toStringTag]

Начальным значением свойства @@toStringTag является строка "Symbol". Это свойство используется в Object.prototype.toString(). Однако из-за того, что у Symbol есть свой собственный метод toString(), это свойство не используется если не будет вызван Object.prototype.toString.call() с символом thisArg.

Методы экземпляра

Symbol.prototype.toString()

Возвращает строку с описанием символа. Переопределяет метод Object.prototype.toString().

Symbol.prototype.valueOf()

Возвращает символ. Переопределяет метод Object.prototype.valueOf().

Symbol.prototype[@@toPrimitive]()

Возвращает символ.

Примеры

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

Оператор typeof позволяет определять символы.

js
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";

Преобразование типов с символами

При преобразовании символов необходимо учитывать следующее.

  • При попытке конвертировать символ в число возникает исключение TypeError (например, +sym или sym | 0).
  • Результатом нестрогого сравнения Object(sym) == sym будет true.
  • Symbol("foo") + "bar" вызывает исключение TypeError (невозможно преобразовать символ в строку). Это помогает избежать случайного создания строкового свойства объекта из символа.
  • Более "безопасный" вызов String(sym) работает с символами как вызов Symbol.prototype.toString(). Обратите внимание, что в то же время new String(sym) вызовет исключение.

Символы и конструкция for...in

Символы не перечисляются при итерации for...in. В дополнение к этому, Object.getOwnPropertyNames() не вернёт свойства символьного объекта. Тем не менее, их можно получить с помощью Object.getOwnPropertySymbols().

js
const obj = {};

obj[Symbol("a")] = "a";
obj[Symbol.for("b")] = "b";
obj["c"] = "c";
obj.d = "d";

for (const i in obj) {
  console.log(i);
}
// "c" "d"

Символы и JSON.stringify()

При использовании JSON.stringify() полностью игнорируются свойства с символьными ключами:

js
JSON.stringify({ [Symbol("foo")]: "foo" }); // '{}'

Более подробная информация в JSON.stringify().

Объекты-обёртки для символов в качестве ключей свойств

Когда объект-обёртка символа используется в качестве ключа свойства, этот объект приводится к символу, который он оборачивает:

js
const sym = Symbol("foo");
const obj = { [sym]: 1 };
obj[sym]; // 1
obj[Object(sym)]; // тоже 1

Спецификации

Specification
ECMAScript Language Specification
# sec-symbol-objects

Совместимость с браузерами

BCD tables only load in the browser

Смотрите также