ES6 в действии: Символы и их использование

ES6 в действии: Символы и их использование

От автора: хотя ES2015 представил многие функции языка, которые уже определенное время значились в списках пожеланий разработчиков, есть некоторые новые функции, которые известны и понятны немного меньше. А преимущества их использования еще меньше — например, символы.

Символ — это новый примитивный тип, уникальный токен, который гарантировано никогда не пересекается с другим символом. В этом смысле вы могли бы представить символы как своего рода UUID (универсально уникальный идентификатор). Давайте рассмотрим, как работает ES6 Simbol, и что мы с ним можем делать.

Создание новых символов

Создать новый символ очень просто, для этого нужно просто вызвать функцию Symbol. Обратите внимание, что это стандартная функция, а не конструктор объекта. Попытка вызвать ее с помощью оператора new приведет к TypeError. Каждый раз, когда вы вызываете функцию Symbol, вы получите новое и полностью уникальное значение.

const foo = Symbol();
const bar = Symbol(); foo === bar
// <-- false

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

let foo = Symbol('baz');
let bar = Symbol('baz'); foo === bar
// <-- false
console.log(foo);
// <-- Symbol(baz)

Что мы можем сделать с помощью символов?

Символы могут быть хорошей заменой для строк или целых чисел в качестве констант класса / модуля:

class Application { constructor(mode) { switch (mode) { case Application.DEV: // Настройка приложения для среды разработки break; case Application.PROD: // Настройка приложения для рабочей среды break; case default: throw new Error('Invalid application mode: ' + mode); } }
} Application.DEV = Symbol('dev');
Application.PROD = Symbol('prod'); // Пример использования
const app = new Application(Application.DEV);

Строки и целые числа не являются уникальными значениями; такие значения, как число 2 или строка development, например, могут также использоваться в других местах программы для разных целей. Использование символов означает, что мы можем быть более уверенными в уникальности предоставляемого значения.

Еще одно интересное использование символов — это ключи свойств объекта. Если вы когда-либо использовали такой объект JavaScript, как hashmap (ассоциативный массив в терминах PHP или словарь в Python), вы знакомы с получением / настройкой свойств с использованием обозначения квадратных скобок:

const data = []; data['name'] = 'Ted Mosby';
data['nickname'] = 'Teddy Westside';
data['city'] = 'New York';

Используя обозначение квадратных скобок, мы также можем использовать символ как ключ свойства. Это дает нам несколько преимуществ. Во-первых, вы можете быть уверены, что ключи на основе символов никогда не будут пересекаться, в отличие от строковых ключей, которые могут конфликтовать с ключами для существующих свойств или методов объекта. Во-вторых, они не будут перечисляться в циклах for … in и, соответственно, они игнорируются такими функциями, как Object.keys(), Object.getOwnPropertyNames() и JSON.stringify(). Это делает их идеальными для свойств, которые вы не хотите включать в сериализацию объекта.

const user = {};
const email = Symbol(); user.name = 'Fred';
user.age = 30;
user[email] = 'fred@example.com'; Object.keys(user);
// <-- Array [ "name", "age" ] Object.getOwnPropertyNames(user);
// <-- Array [ "name", "age" ] JSON.stringify(user);
// <-- "{"name":"Fred","age":30}"

Однако стоит отметить, что использование символов в качестве ключей не гарантирует конфиденциальности. Есть несколько новых инструментов, которые позволяют получить доступ к символьным ключам свойств. Object.getOwnPropertySymbols() возвращает массив любых символьных ключей свойств, в то время, как Reflect.ownKeys() возвращает массив всех ключей, включая символы.

Object.getOwnPropertySymbols(user);
// <-- Array [ Symbol() ] Reflect.ownKeys(user)
// <-- Array [ "name", "age", Symbol() ]

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

Поскольку свойства с символьными ключами эффективно невидимы для кода до ES6, они идеально подходят для добавления новых функциональных возможностей к существующим типам JavaScript без нарушения обратной совместимости. Так называемые «известные» символы являются предопределенными свойствами функции Symbol, которые используются для настройки поведения определенных функций языка и для реализации новых функциональных возможностей, таких как итераторы.

Symbol.iterator — это известный символ, который используется для назначения специального метода объектам, который позволяет его обрабатывать через цикл:

const band = ['Freddy', 'Brian', 'John', 'Roger'];
const iterator = band[Symbol.iterator](); iterator.next().value;
// <-- { value: "Freddy", done: false }
iterator.next().value;
// <-- { value: "Brian", done: false }
iterator.next().value;
// <-- { value: "John", done: false }
iterator.next().value;
// <-- { value: "Roger", done: false }
iterator.next().value;
// <-- { value: undefined, done: true }

Встроенные типы String, Array, TypedArray, Map и Set все по умолчанию содержат метод Symbol.iterator, который вызывается, когда экземпляр одного из этих типов используется в цикле for … of или с оператором распространения. Браузеры также начинают использовать ключ Symbol.iterator, чтобы дать возможность таким же образом обрабатывать через цикл такие структуры DOM, как NodeList и HTMLCollection.

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

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

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

Symbol.keyFor(symbol) позволяет получить ключ для данного символа. Вызов метода с символом, который не существует в реестре, возвращает undefined:

const debbie = Symbol.for('user');
const mike = Symbol.for('user'); debbie === mike
// <-- true Symbol.keyFor(debbie);
// <-- "user"

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

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

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

Поддержка браузерами

Если вы хотите поэкспериментировать с символами, общая поддержка браузерами находит на достаточном уровне. Текущие версии Chrome, Firefox, Microsoft Edge и Opera поддерживают тип Symbol изначально, наряду с Android 5.1 и iOS 9 на мобильных устройствах. Существуют также полифиллы, если вам нужно обеспечить поддержку в старых браузерах.

Заключение

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

Автор: Nilson Jacques

Источник: https://www.sitepoint.com/

Редакция: Команда webformyself.