Symbol
Symbol
— это встроенный объект, конструктор которого возвращает значение примитивного типа symbol
. Такие значения называют символьными значениями (Symbol value) или просто символами (Symbol), их основная особенность в том, что они гарантируют уникальность. Символы часто используются в качестве уникальных ключей объекта. Они не пересекаются с ключами, которые могут быть добавлены другим кодом, а также скрыты от доступа из другого кода. Это обеспечивает возможность слабой инкапсуляции или слабую форму сокрытия информации.
Каждый вызов Symbol()
гарантированно возвращает уникальный символ. Каждый вызов Symbol.for("key")
всегда будет возвращать один и тот же символ для указанного значения "key"
. При вызове Symbol.for("key")
осуществляется поиск в глобальном реестре символов. Если символ найден, то он возвращается, в противном случае создаётся новый символ, добавляется в глобальный реестр под заданным ключом и возвращается.
Описание
Чтобы создать новое символьное значение, достаточно написать Symbol()
, указав по желанию строку в качестве описания:
const sym1 = Symbol();
const sym2 = Symbol("foo");
const sym3 = Symbol("foo");
Код выше создаёт три новых символа. Обратите внимание, что Symbol("foo")
не выполняет приведение строки "foo" к символу. Это выражение создаёт каждый раз новый символ:
Symbol("foo") === Symbol("foo"); // false
Код ниже с оператором new
вызовет исключение TypeError
:
const sym = new Symbol(); // TypeError
Это удерживает разработчиков от создания явного объекта-обёртки Symbol
вместо нового символьного значения, но может быть неожиданным, так как создание явных объектов-обёрток для примитивных типов доступно (например, new Boolean
, new String
, new Number
).
Если действительно необходимо обернуть символ в объект, можно использовать функцию Object()
:
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)
принимает символьное значение и возвращает соответствующий ему строковый ключ. Каждый из них является обратным другому, поэтому следующее выражение истинно:
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
позволяет определять символы.
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()
.
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()
полностью игнорируются свойства с символьными ключами:
JSON.stringify({ [Symbol("foo")]: "foo" }); // '{}'
Более подробная информация в JSON.stringify()
.
Объекты-обёртки для символов в качестве ключей свойств
Когда объект-обёртка символа используется в качестве ключа свойства, этот объект приводится к символу, который он оборачивает:
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
Смотрите также
- Полифил
Symbol
вcore-js
typeof
- Типы и структуры данных JavaScript
- ES6 In Depth: Symbols на hacks.mozilla.org (2015)