Что означает символ в javascript
Перейти к содержимому

Что означает символ в javascript

  • автор:

Symbol

Символ (анг. Symbol) — это уникальный и неизменяемый тип данных, который может быть использован как идентификатор для свойств объектов. Символьный объект (анг. symbol object) — это объект-обёртка (англ. wrapper) для примитивного символьного типа.

Синтаксис

Параметры

Необязательный, строка. Описание символа, которое может быть использовано во время отладки, но не для доступа к самому символу.

Описание

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

Код выше создаёт три новых символа. Заметьте, что Symbol(«foo») не выполняет приведение (англ. coercion) строки «foo» к символу. Это выражение создаёт каждый раз новый символ:

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

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

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

Разделяемые символы в глобальном символьном реестре

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

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

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

Свойства

Содержит длину, всегда равную 0 (нулю).

Содержит прототип конструктора Symbol .

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

В добавок к вашим собственным символам, JavaScript имеет несколько встроенных символов, представляющих внутренние механизмы языка, которые не были доступны разработчикам в версиях ECMAScript 5 и более ранних. Эти символы доступны посредством следующих свойств:

Итерационные символы

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

Символы регулярных выражений

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

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

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

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

Другие символы

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

Булево значение, показывающее, должен ли объект быть сведён к плоскому представлению (англ. flatten) в виде массива его элементов функцией Array.prototype.concat() .

Массив строковых имён свойств. Позволяет скрыть свойства от инструкции with (прежде всего для обратной совместимости).

Метод, определяющий конструктор для порождённых объектов.

Метод, преобразующий объект в примитив (примитивное значение).

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

Методы

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

Получает по разделяемому символу его ключ из глобального реестра символов.

Name already in use

javascript-tutorial-ru / 1-js / 10-es-modern / 8-symbol / article.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink
  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Тип данных Symbol

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

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

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

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

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

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

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

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

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

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

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

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

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

Тип данных Symbol

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

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

Символы

«Символ» представляет собой уникальный идентификатор.

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

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

Символы гарантированно уникальны. Даже если мы создадим множество символов с одинаковым описанием, это всё равно будут разные символы. Описание – это просто метка, которая ни на что не влияет.

Например, вот два символа с одинаковым описанием – но они не равны:

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

Большинство типов данных в JavaScript могут быть неявно преобразованы в строку. Например, функция alert принимает практически любое значение, автоматически преобразовывает его в строку, а затем выводит это значение, не сообщая об ошибке. Символы же особенные и не преобразуются автоматически.

К примеру, alert ниже выдаст ошибку:

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

Если же мы действительно хотим вывести символ с помощью alert , то необходимо явно преобразовать его с помощью метода .toString() , вот так:

Или мы можем обратиться к свойству symbol.description , чтобы вывести только описание:

«Скрытые» свойства

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

Например, мы работаем с объектами user , которые принадлежат стороннему коду. Мы хотим добавить к ним идентификаторы.

Используем для этого символьный ключ:

Почему же лучше использовать Symbol("id") , а не строку "id" ?

Так как объект user принадлежит стороннему коду, и этот код также работает с ним, то нам не следует добавлять к нему какие-либо поля. Это небезопасно. Но к символу сложно нечаянно обратиться, сторонний код вряд ли его вообще увидит, и, скорее всего, добавление поля к объекту не вызовет никаких проблем.

Кроме того, предположим, что другой скрипт для каких-то своих целей хочет записать собственный идентификатор в объект user . Этот скрипт может быть какой-то JavaScript-библиотекой, абсолютно не связанной с нашим скриптом.

Сторонний код может создать для этого свой символ Symbol("id") :

Конфликта между их и нашим идентификатором не будет, так как символы всегда уникальны, даже если их имена совпадают.

А вот если бы мы использовали строку "id" вместо символа, то тогда был бы конфликт:

Символы в литеральном объекте

Если мы хотим использовать символ при литеральном объявлении объекта <. >, его необходимо заключить в квадратные скобки.

Это вызвано тем, что нам нужно использовать значение переменной id в качестве ключа, а не строку «id».

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

Свойства, чьи ключи – символы, не перебираются циклом for..in .

Это – часть общего принципа «сокрытия символьных свойств». Если другая библиотека или скрипт будут работать с нашим объектом, то при переборе они не получат ненароком наше символьное свойство. Object.keys(user) также игнорирует символы.

А вот Object.assign, в отличие от цикла for..in , копирует и строковые, и символьные свойства:

Здесь нет никакого парадокса или противоречия. Так и задумано. Идея заключается в том, что, когда мы клонируем или объединяем объекты, мы обычно хотим скопировать все свойства (включая такие свойства с ключами-символами, как, например, id в примере выше).

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

Итак, как мы видели, обычно все символы уникальны, даже если их имена совпадают. Но иногда мы наоборот хотим, чтобы символы с одинаковыми именами были одной сущностью. Например, разные части нашего приложения хотят получить доступ к символу "id" , подразумевая именно одно и то же свойство.

Для этого существует глобальный реестр символов. Мы можем создавать в нём символы и обращаться к ним позже, и при каждом обращении нам гарантированно будет возвращаться один и тот же символ.

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

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

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

В некоторых языках программирования, например, Ruby, на одно имя (описание) приходится один символ, и не могут существовать разные символы с одинаковым именем.

В JavaScript, как мы видим, это утверждение верно только для глобальных символов.

Symbol.keyFor

Для глобальных символов, кроме Symbol.for(key) , который ищет символ по имени, существует обратный метод: Symbol.keyFor(sym) , который, наоборот, принимает глобальный символ и возвращает его имя.

Внутри метода Symbol.keyFor используется глобальный реестр символов для нахождения имени символа. Так что этот метод не будет работать для неглобальных символов. Если символ неглобальный, метод не сможет его найти и вернёт undefined .

Впрочем, для любых символов доступно свойство description .

Системные символы

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

Эти символы перечислены в спецификации в таблице Well-known symbols:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • …и так далее.

В частности, Symbol.toPrimitive позволяет описать правила для объекта, согласно которым он будет преобразовываться к примитиву. Мы скоро увидим его применение.

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

Итого

Символ (symbol) – примитивный тип данных, использующийся для создания уникальных идентификаторов.

Символы создаются вызовом функции Symbol() , в которую можно передать описание (имя) символа.

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

Символы имеют два основных варианта использования:

«Скрытые» свойства объектов.

Если мы хотим добавить свойство в объект, который «принадлежит» другому скрипту или библиотеке, мы можем создать символ и использовать его в качестве ключа. Символьное свойство не появится в for..in , так что оно не будет нечаянно обработано вместе с другими. Также оно не будет модифицировано прямым обращением, так как другой скрипт не знает о нашем символе. Таким образом, свойство будет защищено от случайной перезаписи или использования.

Так что, используя символьные свойства, мы можем спрятать что-то нужное нам, но что другие видеть не должны.

Symbol type

By specification, only two primitive types may serve as object property keys:

  • string type, or
  • symbol type.

Otherwise, if one uses another type, such as number, it’s autoconverted to string. So that obj[1] is the same as obj["1"] , and obj[true] is the same as obj["true"] .

Until now we’ve been using only strings.

Now let’s explore symbols, see what they can do for us.

Symbols

A “symbol” represents a unique identifier.

A value of this type can be created using Symbol() :

Upon creation, we can give symbols a description (also called a symbol name), mostly useful for debugging purposes:

Symbols are guaranteed to be unique. Even if we create many symbols with exactly the same description, they are different values. The description is just a label that doesn’t affect anything.

For instance, here are two symbols with the same description – they are not equal:

If you are familiar with Ruby or another language that also has some sort of “symbols” – please don’t be misguided. JavaScript symbols are different.

So, to summarize, a symbol is a “primitive unique value” with an optional description. Let’s see where we can use them.

Most values in JavaScript support implicit conversion to a string. For instance, we can alert almost any value, and it will work. Symbols are special. They don’t auto-convert.

For instance, this alert will show an error:

That’s a “language guard” against messing up, because strings and symbols are fundamentally different and should not accidentally convert one into another.

If we really want to show a symbol, we need to explicitly call .toString() on it, like here:

Or get symbol.description property to show the description only:

“Hidden” properties

Symbols allow us to create “hidden” properties of an object, that no other part of code can accidentally access or overwrite.

For instance, if we’re working with user objects, that belong to a third-party code. We’d like to add identifiers to them.

Let’s use a symbol key for it:

What’s the benefit of using Symbol("id") over a string "id" ?

As user objects belong to another codebase, it’s unsafe to add fields to them, since we might affect pre-defined behavior in that other codebase. However, symbols cannot be accessed accidentally. The third-party code won’t be aware of newly defined symbols, so it’s safe to add symbols to the user objects.

Also, imagine that another script wants to have its own identifier inside user , for its own purposes.

Then that script can create its own Symbol("id") , like this:

There will be no conflict between our and their identifiers, because symbols are always different, even if they have the same name.

…But if we used a string "id" instead of a symbol for the same purpose, then there would be a conflict:

Symbols in an object literal

If we want to use a symbol in an object literal <. >, we need square brackets around it.

That’s because we need the value from the variable id as the key, not the string “id”.

Symbols are skipped by for…in

Symbolic properties do not participate in for..in loop.

Object.keys(user) also ignores them. That’s a part of the general “hiding symbolic properties” principle. If another script or a library loops over our object, it won’t unexpectedly access a symbolic property.

In contrast, Object.assign copies both string and symbol properties:

There’s no paradox here. That’s by design. The idea is that when we clone an object or merge objects, we usually want all properties to be copied (including symbols like id ).

Global symbols

As we’ve seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be same entities. For instance, different parts of our application want to access symbol "id" meaning exactly the same property.

To achieve that, there exists a global symbol registry. We can create symbols in it and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol.

In order to read (create if absent) a symbol from the registry, use Symbol.for(key) .

That call checks the global registry, and if there’s a symbol described as key , then returns it, otherwise creates a new symbol Symbol(key) and stores it in the registry by the given key .

Symbols inside the registry are called global symbols. If we want an application-wide symbol, accessible everywhere in the code – that’s what they are for.

In some programming languages, like Ruby, there’s a single symbol per name.

In JavaScript, as we can see, that’s true for global symbols.

Symbol.keyFor

We have seen that for global symbols, Symbol.for(key) returns a symbol by name. To do the opposite – return a name by global symbol – we can use: Symbol.keyFor(sym) :

The Symbol.keyFor internally uses the global symbol registry to look up the key for the symbol. So it doesn’t work for non-global symbols. If the symbol is not global, it won’t be able to find it and returns undefined .

That said, all symbols have the description property.

System symbols

There exist many “system” symbols that JavaScript uses internally, and we can use them to fine-tune various aspects of our objects.

They are listed in the specification in the Well-known symbols table:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • …and so on.

For instance, Symbol.toPrimitive allows us to describe object to primitive conversion. We’ll see its use very soon.

Other symbols will also become familiar when we study the corresponding language features.

Summary

Symbol is a primitive type for unique identifiers.

Symbols are created with Symbol() call with an optional description (name).

Symbols are always different values, even if they have the same name. If we want same-named symbols to be equal, then we should use the global registry: Symbol.for(key) returns (creates if needed) a global symbol with key as the name. Multiple calls of Symbol.for with the same key return exactly the same symbol.

Symbols have two main use cases:

“Hidden” object properties.

If we want to add a property into an object that “belongs” to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in for..in , so it won’t be accidentally processed together with other properties. Also it won’t be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite.

So we can “covertly” hide something into objects that we need, but others should not see, using symbolic properties.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *