Практическое руководство по символам в JavaScript

Практическое руководство по символам в JavaScript

От автора: в ES6 появились символы Javasript, как способ предотвращения конфликтов имен свойств. В качестве дополнительного бонуса символы также позволяют моделировать закрытые свойства в JavaScript 2015-2019.

Вступление

Самый простой способ создать символ в JavaScript — это вызвать функцию Symbol(). 2 ключевых вещи, которые делают символы такими особенными:

Символы могут использоваться как ключи объекта. В качестве ключей объекта могут использоваться только строки и символы.

Не существует двух одинаковых символов.

const symbol1 = Symbol();
const symbol2 = Symbol(); symbol1 === symbol2; // false const obj = {};
obj[symbol1] = 'Hello';
obj[symbol2] = 'World'; obj[symbol1]; // 'Hello'
obj[symbol2]; // 'World'

Хотя при вызове Symbol() создается впечатление, что символы являются объектами, символы на самом деле являются примитивным типом JavaScript. Использование Symbol в качестве конструктора с new выдает ошибку.

const symbol1 = Symbol(); typeof symbol1; // 'symbol'
symbol1 instanceof Object; // false // Throws "TypeError: Symbol is not a constructor"
new Symbol();

Descriptions

Функция Symbol() принимает один параметр, строка description. description предназначен только для целей отладки — description отображается в символе toString(). Однако два символа с одинаковым description не равны.

const symbol1 = Symbol('my symbol');
const symbol2 = Symbol('my symbol'); symbol1 === symbol2; // false
console.log(symbol1); // 'Symbol(my symbol)'

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

const symbol1 = Symbol.for('test');
const symbol2 = Symbol.for('test'); symbol1 === symbol2; // true
console.log(symbol1); // 'Symbol(test)'

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

Конфликты имен

Первым встроенным символом в JavaScript был символ Symbol.iterator ES6. Объект с функцией Symbol.iterator считается итеративным. Это означает, что вы можете использовать этот объект как правую часть for/of цикла.

const fibonacci = { [Symbol.iterator]: function*() { let a = 1; let b = 1; let temp; yield b; while (true) { temp = a; a = a + b; b = temp; yield b; } }
}; // Prints every Fibonacci number less than 100
for (const x of fibonacci) { if (x >= 100) { break; } console.log(x);
}

Почему Symbol.iterator символ, а не строка? Предположим, вместо использования итеративной спецификации Symbol.iterator проверяется наличие строкового свойства ‘iterator’. Кроме того, предположим, что у вас был следующий класс, который должен был быть повторяемым.

class MyClass { constructor(obj) { Object.assign(this, obj); } iterator() { const keys = Object.keys(this); let i = 0; return (function*() { if (i >= keys.length) { return; } yield keys[i++]; })(); }
}

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

const obj = new MyClass({ iterator: 'not a function' });

Если бы вы использовали for/of с obj, JavaScript выдал бы TypeError: obj is not iterable. Потому что пользовательская функция iterator перезаписывает свойство итератора класса. Это похоже на проблему безопасности с загрязнением прототипа, когда простое копирование пользовательских данных может вызвать проблемы со специальными свойствами, такими как __proto__и constructor.

Ключевой шаблон здесь заключается в том, что символы обеспечивают четкое разделение между данными пользователя и данными программы в объектах. Поскольку символы не могут быть представлены в JSON, нет риска передачи данных в Express API с ненадлежащим свойством Symbol.iterator. В объектах, которые смешивают пользовательские данные со встроенными функциями и методами, например в моделях Mongoose, вы можете использовать символы, чтобы гарантировать, что пользовательские данные не конфликтуют со встроенными функциями.

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

Поскольку нет двух одинаковых символов, символы являются удобным способом имитации закрытых свойств в JavaScript. Символы не отображаются в Object.keys(), и поэтому, если вы явно не export символ, никакой другой код не может получить доступ к этому свойству, если вы явно не пройдете функцию Object.getOwnPropertySymbols().

function getObj() { const symbol = Symbol('test'); const obj = {}; obj[symbol] = 'test'; return obj;
} const obj = getObj(); Object.keys(obj); // [] // Unless you explicitly have a reference to the symbol, you can't access the
// symbol property.
obj[Symbol('test')]; // undefined // You can still get a reference to the symbol using `getOwnPropertySymbols()`
const [symbol] = Object.getOwnPropertySymbols(obj);
obj[symbol]; // 'test'

Символы также удобны для закрытых свойств, потому что они не отображаются в выходных данных JSON.stringify(). В частности, JSON.stringify() игнорирует ключи и значения символов.

const symbol = Symbol('test');
const obj = { [symbol]: 'test', test: symbol }; JSON.stringify(obj); // "{}"

Что дальше?

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

Автор: Valeri Karpov

Источник: http://thecodebarbarian.com

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