что означает в javascript символ
Особенности использования типа данных Symbol в JavaScript
Символьные примитивы — это одно из новшеств стандарта ES6, которое принесло в JavaScript некоторые ценные возможности. Символы, представленные типом данных Symbol, особенно полезны при использовании их в качестве идентификаторов свойств объектов. В связи с таким сценарием их применения напрашивается вопрос о том, что такого они могут, чего не могут строки.
В материале, перевод которого мы сегодня публикуем, речь пойдёт о типе данных Symbol в JavaScript. Начнём мы с обзора некоторых возможностей JavaScript, в которых нужно ориентироваться для того, чтобы разобраться с символами.
Предварительные сведения
Примитивные значения иммутабельны. Их нельзя изменять. Конечно, в переменную, хранящую примитивное значение, можно записать что-то новое. Например, здесь выполняется запись нового значения в переменную x :
В некоторых языках, например — в C, есть концепции передачи аргументов функций по ссылке и по значению. В JavaScript тоже есть нечто подобное. То, как именно организуется работа с данными, зависит от их типа. Если в функцию передают примитивное значение, представленное некоей переменной, а потом изменяют его в этой функции, значение, хранящееся в исходной переменной, при этом не меняется. Однако если в функцию передать объектное значение, представленное переменной, и модифицировать его, то изменится и то, что хранится в этой переменной.
Рассмотрим следующий пример:
Однако конструирование объектных значений, внешне выглядящих одинаково, не приведёт к тому, что получатся сущности, при сравнении которых будет выявлено их равенство друг другу. Проверить это можно так:
Объекты играют фундаментальную роль в JavaScript. Они применяются буквально повсюду. Например, часто их используют в виде коллекций вида ключ/значение. Но до появления типа данных Symbol в качестве ключей объектов можно было применять лишь строки. В этом крылось серьёзное ограничение использования объектов в виде коллекций. При попытке назначения нестрокового значения в виде ключа объекта это значение приводилось к строке. Убедиться в этом можно так:
Кстати, хотя это немного уводит нас от темы символов, хочется отметить, что структура данных Map была создана для того чтобы позволить использовать хранилища данных формата ключ/значение в ситуациях, когда ключ не является строкой.
Что такое символ?
Теперь, когда мы выяснили особенности примитивных значений в JavaScript, мы наконец готовы к тому, чтобы приступить к разговору о символах. Символ — это уникальное примитивное значение. Если подходить к символам с этой позиции, то можно заметить, что символы в этом плане похожи на объекты, так как создание нескольких экземпляров символов приведёт к созданию разных значений. Но символы, кроме того, являются иммутабельными примитивными значениями. Вот пример работы с символами:
При создании экземпляра символа можно воспользоваться необязательным первым строковым аргументом. Этот аргумент представляет собой описание символа, которое предназначено для использования при отладке. На сам символ это значение не влияет.
Символы как ключи свойств объектов
Символы можно использовать в качестве ключей свойств объектов. Это очень важно. Вот пример использования их в таком качестве:
На первый взгляд может показаться, что вышеописанные особенности символов позволяют использовать их для создания приватных свойств JS-объектов. Во многих других языках программирования можно создавать скрытые свойства объектов с использованием классов. Отсутствие этой возможности уже давно считается одним из недостатков JavaScript.
К сожалению, код, который работает с объектами, может беспрепятственно обращаться к их строковым ключам. Код может обращаться и к ключам, заданным символами, причём, даже в том случае, если у кода, из которого работают с объектом, нет доступа к соответствующему символу. Например, с помощью метода Reflect.ownKeys() можно получить список всех ключей объекта, и тех, что являются строками, и тех, что являются символами:
Обратите внимание на то, что в настоящее время ведётся работа над тем, чтобы оснастить классы возможностью использования приватных свойств. Эта возможность называется Private Fields (приватные поля). Она, правда, не затрагивает абсолютно все объекты, относясь лишь к тем из них, которые созданы на основе предварительно подготовленных классов. Поддержка приватных полей уже имеется в браузере Chrome версии 72 и старше.
Предотвращение коллизий имён свойств объектов
Символы, конечно, не добавляют в JavaScript возможностей по созданию приватных свойств объектов, но они являются ценным новшеством языка по другим причинам. А именно, они полезны в ситуациях, когда неким библиотекам нужно добавлять свойства в объекты, описанные за их пределами, и при этом не опасаться коллизии имён свойств объектов.
Если же воспользоваться в нашем примере символами, то каждая библиотека может сгенерировать, при инициализации, нужные ей символы. Затем эти символы могут быть использованы для назначения свойств объектам и для доступа к этим свойствам.
Именно глядя на подобный сценарий можно ощутить пользу от появления символов в JavaScript.
Однако тут может возникнуть вопрос, касающийся использования библиотеками, для имён свойств объектов, случайных строк или строк, со сложной структурой, включающих в себя, например, название библиотеки. Подобные строки могут образовывать нечто вроде пространств имён для идентификаторов, используемых библиотеками. Например, это может выглядеть так:
В общем-то, можно поступить и так. Подобные подходы, на самом деле, очень похожи на то, что происходит при использовании символов. И если, используя случайные идентификаторы или пространства имён, пара библиотек не сгенерирует, по воле случая, одинаковые имена свойств, то проблем с именами не будет.
Проницательный читатель сказал бы сейчас, что два рассматриваемых подхода к именованию свойств объектов не являются полностью эквивалентными. У имён свойств, которые формируются случайным образом или с использованием пространств имён, есть недостаток: соответствующие ключи очень легко обнаружить, особенно если в коде выполняется перебор ключей объектов или их сериализация. Рассмотрим следующий пример:
Если бы в этой ситуации для имени ключа использовался бы символ, тогда JSON-представление объекта не содержало бы значения символа. Почему это так? Дело в том, что то, что в JavaScript появился новый тип данных, ещё не означает того, что изменения внесены и в спецификацию JSON. JSON поддерживает, в качестве ключей свойств объектов, только строки. При сериализации объекта не делается попыток представить символы в каком-то особом виде.
Рассматриваемую проблему попадания имён свойств в JSON-представление объектов можно решить благодаря использованию Object.defineProperty() :
Однако между использованием имён-символов и имён, созданных с использованием других механизмов, есть одно маленькое различие. Так как строки иммутабельны, а символы гарантированно уникальны, всегда есть возможность того, что кто-то, перебрав все возможные сочетания символов в строке, вызовет коллизию имён. С математической точки зрения это означает, что символы действительно дают нам ценную возможность, которая отсутствует у строк.
Имитация приватных свойств
Вот интересный подход, который можно использовать для имитации приватных свойств объектов. Этот подход предусматривает применение ещё одной современной возможности JavaScript — прокси-объектов. Такие объекты служат обёртками для других объектов, которые позволяют программисту вмешиваться в действия, выполняемые с этими объектами.
Прокси-объекты предлагают много способов для перехвата действий, выполняемых над объектами. Нас интересует возможность управления операциями чтения ключей объекта. В подробности о прокси-объектах мы тут углубляться не будем. Если вам они интересны — взгляните на эту публикацию.
Тут надо отметить, что в Node.js есть одна особенность, нарушающая приватность прокси-объектов. Эта особенность не существует в самом языке, поэтому она не актуальна для других сред выполнения JS, таких, как браузер. Дело в том, что эта особенность позволяет получать доступ к объекту, скрытому за прокси-объектом, при наличии доступа к прокси-объекту. Вот пример, демонстрирующий возможность обхода механизмов, показанных в предыдущем фрагменте кода:
Итоги
Уважаемые читатели! Пользуетесь ли вы символами в своих JavaScript-проектах?
Тип данных Symbol
По спецификации, в качестве ключей для свойств объекта могут использоваться только строки или символы. Ни числа, ни логические значения не подходят, разрешены только эти два типа данных.
До сих пор мы видели только строки. Теперь давайте разберём символы, увидим, что хорошего они нам дают.
Символы
«Символ» представляет собой уникальный идентификатор.
Создаются новые символы с помощью функции Symbol() :
При создании символу можно дать описание (также называемое имя), в основном использующееся для отладки кода:
Символы гарантированно уникальны. Даже если мы создадим множество символов с одинаковым описанием, это всё равно будут разные символы. Описание – это просто метка, которая ни на что не влияет.
Например, вот два символа с одинаковым описанием – но они не равны:
Если вы знаете Ruby или какой-то другой язык программирования, в котором есть своего рода «символы» – пожалуйста, будьте внимательны. Символы в JavaScript имеют свои особенности, и не стоит думать о них, как о символах в Ruby или в других языках.
Большинство типов данных в JavaScript могут быть неявно преобразованы в строку. Например, функция alert принимает практически любое значение, автоматически преобразовывает его в строку, а затем выводит это значение, не сообщая об ошибке. Символы же особенные и не преобразуются автоматически.
К примеру, alert ниже выдаст ошибку:
Это – языковая «защита» от путаницы, ведь строки и символы – принципиально разные типы данных и не должны неконтролируемо преобразовываться друг в друга.
«Скрытые» свойства
Символы позволяют создавать «скрытые» свойства объектов, к которым нельзя нечаянно обратиться и перезаписать их из других частей программы.
Используем для этого символьный ключ:
Так как объект user принадлежит стороннему коду, и этот код также работает с ним, то нам не следует добавлять к нему какие-либо поля. Это небезопасно. Но к символу сложно нечаянно обратиться, сторонний код вряд ли его вообще увидит, и, скорее всего, добавление поля к объекту не вызовет никаких проблем.
Сторонний код может создать для этого свой символ Symbol(«id») :
Конфликта между их и нашим идентификатором не будет, так как символы всегда уникальны, даже если их имена совпадают.
А вот если бы мы использовали строку «id» вместо символа, то тогда был бы конфликт:
Символы в литеральном объекте
Если мы хотим использовать символ при литеральном объявлении объекта <. >, его необходимо заключить в квадратные скобки.
Это вызвано тем, что нам нужно использовать значение переменной id в качестве ключа, а не строку «id».
Символы игнорируются циклом for…in
Это – часть общего принципа «сокрытия символьных свойств». Если другая библиотека или скрипт будут работать с нашим объектом, то при переборе они не получат ненароком наше символьное свойство. Object.keys(user) также игнорирует символы.
Здесь нет никакого парадокса или противоречия. Так и задумано. Идея заключается в том, что, когда мы клонируем или объединяем объекты, мы обычно хотим скопировать все свойства (включая такие свойства с ключами-символами, как, например, id в примере выше).
Глобальные символы
Для этого существует глобальный реестр символов. Мы можем создавать в нём символы и обращаться к ним позже, и при каждом обращении нам гарантированно будет возвращаться один и тот же символ.
Символы, содержащиеся в реестре, называются глобальными символами. Если вам нужен символ, доступный везде в коде – используйте глобальные символы.
В некоторых языках программирования, например, Ruby, на одно имя (описание) приходится один символ, и не могут существовать разные символы с одинаковым именем.
В JavaScript, как мы видим, это утверждение верно только для глобальных символов.
Symbol.keyFor
Системные символы
Существует множество «системных» символов, использующихся внутри самого JavaScript, и мы можем использовать их, чтобы настраивать различные аспекты поведения объектов.
Эти символы перечислены в спецификации в таблице Well-known symbols:
В частности, Symbol.toPrimitive позволяет описать правила для объекта, согласно которым он будет преобразовываться к примитиву. Мы скоро увидим его применение.
С другими системными символами мы тоже скоро познакомимся, когда будем изучать соответствующие возможности языка.
Итого
Символ (symbol) – примитивный тип данных, использующийся для создания уникальных идентификаторов.
Даже если символы имеют одно и то же имя, это – разные символы. Если мы хотим, чтобы одноимённые символы были равны, то следует использовать глобальный реестр: вызов Symbol.for(key) возвращает (или создаёт) глобальный символ с key в качестве имени. Многократные вызовы команды Symbol.for с одним и тем же аргументом возвращают один и тот же символ.
Символы имеют два основных варианта использования:
Так что, используя символьные свойства, мы можем спрятать что-то нужное нам, но что другие видеть не должны.
Выражения и операторы
Эта глава описывает выражения и операторы языка JavaScript, такие как операторы присваивания, сравнения, арифметические, битовые, логические, строчные, и различные специальные операторы.
Полный и детальный список операторов и выражений также доступен в этом руководстве.
Операторы
В JavaScript есть следующие типы операторов. Данный подраздел описывает каждый тип и содержит информацию об их приоритетах друг над другом.
В свою очередь унарная операция использует один операнд, перед или после оператора:
Операторы присваивания
Существуют также составные операторы присваивания, которые используются для сокращённого представления операций, описанных в следующей таблице:
Деструктуризация
Операторы сравнения
Замечание: (=>) не оператор, а нотация Стрелочных функций.
Арифметические операторы
Арифметические операторы (en-US) используют в качестве своих операндов числа (также литералы или переменные) и в качестве результата возвращают одно числовое значение. Стандартными арифметическими операторами являются сложение (+), вычитание (-), умножение (*), и деление (/). При работе с числами с плавающей точкой эти операторы работают аналогично их работе в большинстве других языках программирования (обратите внимание, что деление на ноль возвращает бесконечность Infinity ). Например:
Кроме того, JavaScript позволяет использовать следующие арифметические операторы, представленные в таблице:
Битовые (поразрядные) операторы
Битовые операторы (en-US) обрабатывают свои операнды как последовательности из 32 бит (нулей и единиц), а не как десятичные, шестнадцатеричные или восьмеричные числа. Например, десятичное число 9 имеет двоичное представление 1001. Битовые операторы выполняют операции над таким двоичным представлением, но результат возвращают как обычное числовое значение JavaScript.
Следующая таблица обобщает битовые операторы JavaScript.
Оператор | Использование | Описание |
---|---|---|
Побитовое И (en-US) | a & b | Возвращает единицу в каждой битовой позиции, для которой соответствующие биты обеих операндов являются единицами. |
Побитовое ИЛИ (en-US) | a | b | Возвращает единицу в каждой битовой позиции, для которой один из соответствующих битов или оба бита операндов являются единицами. |
Исключающее ИЛИ (en-US) | a ^ b | Возвращает единицу в каждой битовой позиции, для которой только один из соответствующих битов операндов является единицей. |
Побитовое НЕ (en-US) | Заменяет биты операнда на противоположные. | |
Сдвиг влево (en-US) | a | Сдвигает a в двоичном представлении на b бит влево, добавляя справа нули. |
Сдвиг вправо с переносом знака (en-US) | a >> b | Сдвигает a в двоичном представлении на b бит вправо, отбрасывая сдвигаемые биты. |
Сдвиг вправо с заполнением нулями (en-US) | a >>> b | Сдвигает a в двоичном представлении на b бит вправо, отбрасывая сдвигаемые биты и добавляя слева нули. |
Битовые логические операторы
Основной смысл работы битовых логических операторов состоит в следующем:
Выражение | Результат | Двоичное описание | |||||
---|---|---|---|---|---|---|---|
15 & 9 | 9 | 1111 & 1001 = 1001 | |||||
15 | 9 | 15 | 1111 | 1001 = 1111 | |||||
15 ^ 9 | 6 | 1111 ^ 1001 = 0110 | |||||
Тип оператора | Операторы |
---|---|
свойство объекта | . [] |
вызов, создание экземпляра объекта | () new |
отрицание, инкремент | ! Более подробная версия данной таблицы, содержащая ссылки и дополнительную информацию по каждому оператору, находится в справочнике JavaScript. ВыраженияВыражением является любой корректный блок кода, который возвращает значение. Концептуально, существуют два типа выражений: те которые присваивают переменной значение, и те, которые вычисляют значение без его присваивания. Код 3 + 4 является примером выражения второго типа. Данное выражение использует оператор «+» для сложения чисел 3 и 4 без присваивания переменной полученного результата 7. Все выражения в JavaScript делятся на следующие категории: Основные выраженияБазовые ключевые слова и основные выражения в JavaScript. Оператор thisИспользуйте ключевое слово this для указания на текущий объект. В общем случае this указывает на вызываемый объект, которому принадлежит данный метод. Используйте this следующим образом: Предположим, функция validate выполняет проверку свойства value некоторого объекта; задан объект, а также верхняя и нижняя граница величины данного свойства: Вы можете вызвать функцию validate для обработчика события onChange для каждого элемента формы, используя this для указания на элемент формы, как это показано в следующем примере: Оператор группировкиУпрощённый синтаксис создания массивов и генераторов[for (x of y) x] Упрощённый синтаксис для массивов. (for (x of y) y) Упрощённый синтаксис для генераторов. Упрощённые синтаксисы существуют во многих языках программирования и позволяют вам быстро собирать новый массив, основанный на существующем. Например: Левосторонние выраженияЗначениям слева назначаются значения справа. Вы можете использовать оператор new для создания экземпляра объекта пользовательского типа или одного из встроенных объектов. Используйте оператор new следующим образом: superКлючевое слово используется, чтобы вызывать функции родительского объекта. Это полезно и с классами для вызова конструктора родителя, например. Оператор расширенияОператор расширения позволяет выражению расширяться в местах с множеством аргументов (для вызовов функций) или множестве элементов (для массивов). Похожим образом оператор работает с вызовами функций:
|