Осваиваем JavaScript ES6 Symbol

Spread the love

Перевод: Mahdhi RezviMastering JavaScript ES6 Symbols

Кредит под залог недвижимости в Краснодаре

Компания АСВ Финанс https://asv-finance.ru/
предлагает практически всех виды займов: под залог земельных участков, домов, квартир, автомобилей.
Так же можем осуществить проверку чистоты интересующего вас объекта недвижимости, либо автомобиля.

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

  • Undefined
  • Null
  • Big Int (добавленный в ES2020)
  • Boolean
  • Number
  • String

Null помечен как одно из примитивных значений в JavaScript, поскольку его действие очевидно. Но в некоторых ситуациях null не так «примитивен», как кажется на первый взгляд! Каждый объект является производным от null, и поэтому оператор typeof возвращает для него тип Объект.

Но с выпуском ES6 в 2015 году был добавлен еще один новый базовый тип – Symbol. Он сильно отличается от предыдущих типов. Этот тип представляет собой просто значение, а не строку, число или даже объект.


Что это за новый тип данных?

Тип данных Symbol – это прежде всего уникальное значение. Его значение – уникальный идентификатор. Вы можете просто вызвать Symbol() и получить уникальный идентификатор. При желании вы также можете передать описание.

Одна из ключевых вещей, которую вы должны помнить, – это то, что Symbol всегда уникальны. Даже если вы передадите одно и то же описание двум Symbol, они все равно будут разными.

Многие люди думают о Symbol как о способе получения уникального значения. Но это только отчасти правда. Хотя Symbol уникальны, вы никогда не получите уникальное значение, отобразив его в консоль. Вы можете только назначить его переменной и использовать эту переменную как уникальный идентификатор.

Другими словами, Symbol не дает уникального значения, такого как идентификатор, который может выглядеть как то так 285af1ae40223348538204f8c3a58f34. Но, скорее, когда вы наберете в консоли Symbol, вы получите Symbol() или Symbol(описание). Помните, что это будет не строка, а простой Symbol.

typeof Symbol()
"symbol"

Вы можете получить строку, вызвав метод toString() для объекта Symbol. Но это тоже даст вам только строковое представление ранее полученного значения.

Что нужно иметь в виду

Нет автоматического преобразования в строку

window.alert() получает параметр строкового типа. Но даже если вы передадите число или даже ноль, вы не получите ошибку. Скорее JavaScript неявно преобразует тип данных в строку и отобразит ее. Но в случае с Symbol JavaScript не преобразует его неявно в строку. Эти два элемента должны быть разделены, поскольку они принципиально разные и не должны случайно превращаться друг в друга.

Использование символов в качестве ключей в литерале объекта

Взгляните на приведенный ниже код.

let id = Symbol("id");
let obj = {
  id: 1
};
// Ключ "id" является строкой а не Symbol

В приведенном выше примере, хотя мы назначили свойство id нашему объекту obj, это не та переменная id, которую мы определили в строке ранее. Чтобы установить переменную id в качестве ключа, мы должны использовать [ ].

let id = Symbol("id");
let obj = {
  [id]: 1
};

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

console.log(obj.id);
//undefined console.log(obj[id]);
//1

Symbol пропускаются обычными функциями проверки объектов

Поскольку Symbol были разработаны для предотвращения конфликтов, символьные свойства не видят их в наиболее распространенных функциях проверки объектов JavaScript, таких как цикл for-in. Символы в качестве ключей свойств также игнорируются в Object.keys(obj) и Object.getOwnPropertyNames(obj).

Также обратите внимание, что свойства символа объекта игнорируются при использовании JSON.stringify().


Глобальный реестр Symbol

Как мы видели выше, Symbol уникальны даже с теми же описаниями, которые мы передаем в качестве параметров. Но могут быть случаи, когда вам понадобится несколько веб-страниц или несколько модулей на одной веб-странице для совместного использования Symbol. В такие моменты вы можете использовать глобальный реестр SymbolGlobal Symbol Registry..

Хотя это звучит как сложная система, интерфейс довольно прост в использовании.

Symbol.for(key)

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

let userId = Symbol.for("user_id");

let userID = Symbol.for("user_id");

console.log(userId === userID);
// true

Symbol.keyFor(sym)

Этот метод выполняет обратную функцию метода Symbol.for(). Он извлекает общий ключ символа из глобального реестра Symbol для данного символа.

let userId = Symbol.for('user_id');
let userName = Symbol.for('username');

console.log(Symbol.keyFor(userId));
// user_id
console.log(Symbol.keyFor(userName));
// username

Случаи использования

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

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

Как можно это решить? У вас есть множество способов решить эту проблему. Но при этом вам нужно будет постараться избегать сортировку уже ранее отсортированного списка. Хотя конечным результатом будет так же отсортированный список, повторная сортировка будет неэффективна, поскольку алгоритм реализует фактический процесс сортировки на уже отсортированном массиве.

Вместо того, чтобы реализовывать решение, которое было бы излишним, мы можем просто установить флаг, чтобы указать, был ли список отсортирован или нет. Это легко позволит нам отсортировать наш список только тогда, когда он не отсортирован.

Реализация могла бы выглядеть примерно так.

if (list.isSorted) {
  sortAlgorithm(list);
}
list.isSorted = true;

Хотя приведенная выше реализация отлично справляется со своей задачей, в некоторых аспектах она не будет работать.

  • Любой другой код, обращающийся к вашему списку, может наткнуться на этот атрибут, например при использовании for-in или Object.keys().
  • Если вы создаете библиотеку для реализации алгоритма сортировки, какой нибудь другой программист может использовать флаг isSorted.

В такой ситуации идеально подходят Symbol, так как они избегают столкновений.

let id = Symbol('id');

let user = {
  name: 'Jane Doe',
  [id]: 1
}

console.log(user[id])
// 1
__________________________
// кто то пытается добавить свое поле id
user.id = '123C'
console.log(user[id])
// 1
console.log(user.id)
// 123C
___________________________
// кто то пытается добавить свое поле Symbol

let id2 = Symbol('id')
user[id2] = '999X'
console.log(user[id])
// 1
console.log(user.id)
// 123C
console.log(user[id2])
// 999X

Системные Symbol

JavaScript использует несколько внутренних Symbol для точной настройки производительности в различных аспектах. Некоторые из них,

  • Symbol.asyncIterator
  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator

Вы можете прочитать о них здесь.


Примечание: Symbol не полностью скрыты.

Вы по-прежнему можете использовать пользовательские методы, такие как Object.getOwnPropertySymbols(obj) и Reflect.ownKeys(obj), для получения Symbol, используемых в качестве ключей объекта. Вы можете спросить, зачем это. Я лично считаю, что Symbol были созданы, чтобы избежать непреднамеренных конфликтов имен. Если кто-то действительно хотел перезаписать ключ символьного свойства, я думаю, он может это сделать в любом случае.


Популярный вопрос, поднятый программистами React относительно Symbol

Во время обсуждения с редактором Bits and Pieces меня попросили затронуть проблему, поднятую в React JS, связанную с Symbol. Ниже приведена ссылка на данную проблему. Использование Symbol в виде ключей в дочерних элементах при рендеринге массивов или итераторов: Запрашиваемый функционал · Issue № 11996

Желаемый функционал

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

Разработчики React должны быть знакомы с концепцией ключей. Ниже приведена выдержка из глоссария документации React (https://reactjs.org/docs/glossary.html#keys).

«Ключ» – это специальный строковый атрибут, который необходимо включать при создании массивов элементов. Ключи помогают React определить, какие элементы были изменены, добавлены или удалены. Ключи должны быть даны элементам внутри массива, чтобы придать элементам стабильную идентичность.

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

Но когда вы добавляете Symbol в качестве ключа к элементу массива, вы получите следующую ошибку.

Image for post
Error Screenshot by Author

Причина указанной выше ошибки заключается в том, что ключи должны быть строкового типа. Если вы помните, что мы ранее рассмотрели, Symbol не относятся к строковому типу и не могут неявно преобразовывать себя в отличие от других примитивных типов данных.

Запрашиваемый функционал состоит в том, чтобы разрешить поддержку Symbol в качестве ключей изначально, поскольку они не преобразуются автоматически в строки.

Почему команда отказалась и закрыла этот вопрос

Дэн Абрамов прокомментировал и закрыл эту проблему, отметив: «Я не вижу практического случая использования Symbol, кроме недоразумения». Он также упоминает, что вы можете просто использовать «идентификатор клиента (customer ID) или имя пользователя» или что-то еще, что связано с данными, которые вы обрабатываете.

Я хотел бы высказать свое мнение с обеих сторон.

Прежде всего, могут быть случаи, когда вы обрабатываете список данных без идентификатора. Это может происходить, когда данные собираются из внешнего интерфейса и отображаются в виде списка. Например простой список номеров. Что делать, если данные вводятся пользователем? Вам нужно будет установить счетчик, чтобы присвоить уникальный ключ каждой записи. Некоторые использовали бы подход присвоения индекса массива каждому элементу, но, как известно, это плохая идея. Вы не можете использовать входное значение в качестве ключа, потому что могут быть повторяющиеся входные данные. Как было предложено, Symbol будет более простой альтернативой.

Но….

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

<ul>
  <li key="{Symbol()}">1</li>
  <li key="{Symbol()}">2</li>
  <li key="{Symbol()}">3</li>
  <li key="{Symbol()}">1</li>
</ul>

Как видите, все 4 ключа уникальны. Когда происходит изменение значения элемента, React знает, какой из них изменился, и запускает перестроение. Но когда дерево перестраивается, ключ этого конкретного элемента снова изменится, поскольку Symbol() будет давать уникальное значение каждый раз, когда он вызывается. Ключ будет разным для каждого рендера, что заставит React повторно re-mount элемент/компонент.

Если вам непонятно, как работает процесс построения дерева и обнаружение изменений в приведенном выше сценарии, просмотрите объяснение, данное в документации.

Вы можете избежать этой проблемы повторного рендеринга, используя глобальный реестр symbol – Symbol.for(key), поскольку каждый раз, когда вы вызываете symbol, вы будете искать symbol в глобальном реестре, и если он будет найден, он будет возвращен.

Но опять же….

В этом подходе тоже что-то не так. Чтобы получить Symbol из глобального реестра, вы должны предоставить ключ символа, который сам по себе является уникальным. Если задуматься, этот ключ сам по себе уникален для идентификации каждого элемента. Тогда зачем нам создавать Symbol в этом случае?

Примечание

Но есть решение, предоставленное Eduardo, когда вы инициализируете объект или массив один раз с помощью Symbol, а затем они никогда не инициализируются повторно. Это означает, что значение не будет пересчитываться при каждом рендеринге, и поэтому значения (Symbol) всегда будут одинаковыми. Но этот подход будет работать только в определенных ситуациях.

import React from 'react';

const TODO = {
  {id: Symbol(), content: 'Wake up'},
  {id: Symbol(), content: 'Go to sleep'},
}

const App = () => {
  return (
    <>
      {
         TODO.map(({id, content}) =>(
           <p key={id}>{content}</p>
         ))
      }
  )
};

export default App;

Обратите внимание, что все указанные решения будут работать, но они вызовут ненужные re-mounts и вызовут нежелательную нагрузку на память и процессор. А наша цель состоит в том, чтобы найти решение с использованием Symbol, которое также будет эффективным.

Если у вас есть какие-либо комментарии, пожалуйста, оставьте их в блоге автора статьи.

Спасибо за чтение и удачного кодирования.

Была ли вам полезна эта статья?
[3 / 4.7]

Spread the love
Подписаться
Уведомление о
guest
2 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Александр
Александр
3 лет назад

Ну вкусно вкусно

Topos
Topos
1 год назад

Нигде не определяется, что такое “(общий) ключ символа”, который, оказывается, можно извлекать из символа. Впрочем, это беда всех текстов на данную тему. Такое впечатление, что все сговорились – не раскрывать главную тайну Симбола.