- Оригинальный адрес:JavaScript Symbols: But Why?
- Оригинальный автор:Thomas Hunter II
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:xionglong58
- Корректор:EdmondWang, Xuyuey
Как новейший примитивный тип, Symbol дает много преимуществ языку JavaScript, особенно при использовании в свойствах объектов. Однако, по сравнению с типом String, какие функции есть у Symbol, которых нет у String?
Прежде чем погрузиться в символы, давайте рассмотрим некоторые функции JavaScript, о которых многие разработчики могут не знать.
задний план
В JavaScript есть два типа данных: базовые типы данных и объекты (объекты также включают в себя функции) Базовые типы данных включают простые типы данных, такие как числа (от целых чисел до чисел с плавающей запятой, от бесконечности до NaN, все относятся к типу Number) ), логическое значение, строка,undefined,null(обратите внимание, что хотяtypeof null === 'object',nullвсе еще примитивный тип данных).
Значение примитивного типа данных является неизменяемым, то есть исходное значение переменной не может быть изменено. КонечноМогуПереназначьте переменную. Например, кодlet x = 1; x++;, хотя вы изменили переменную, переназначивx, но исходное значение переменной1До сих пор не изменились.
Некоторые языки, такие как C, имеют концепцию передачи по ссылке и передачи по значению. JavaScript имеет аналогичную концепцию, которая выводится из типа передаваемых данных. Если значение передается в функцию, переназначение его внутри функции не изменяет его значение в месте вызова. Однако, если выИсправлятьзначение базовых данных, тогда модифицированное значениеМогуизменяется там, где он вызывается.
Рассмотрим следующий пример:
function primitiveMutator(val) {
val = val + 1;
}
let x = 1;
primitiveMutator(x);
console.log(x); // 1
function objectMutator(val) {
val.prop = val.prop + 1;
}
let obj = { prop: 1 };
objectMutator(obj);
console.log(obj.prop); // 2
основные типы данных (NaNкроме) всегда точно равен другому примитивному типу данных с тем же значением. следующим образом:
const first = "abc" + "def";
const second = "ab" + "cd" + "ef";
console.log(first === second); // true
Однако создание двух непримитивных типов данных с одинаковым значением даетне равныйрезультат. Мы видим, что происходит:
const obj1 = { name: "Intrinsic" };
const obj2 = { name: "Intrinsic" };
console.log(obj1 === obj2); // false
// 但是,当两者的 .name 属性为基本数据类型时 console.log(obj1.name === obj2.name); // true
Объекты играют важную роль в JavaScript, почтигде угодноИх можно увидеть. Объекты обычно представляют собой наборы пар ключ/значение, однако самым большим ограничением этой формы является то, что ключи объектов могут быть только строками, и это ограничение не было устранено до появления Symbol. Если мы используем нестроковое значение в качестве ключа объекта, значение будет приведено к строке. Это принуждение можно увидеть в следующей программе:
const obj = {};
obj.foo = 'foo';
obj['bar'] = 'bar';
obj[2] = 2;
obj[{}] = 'someobj';
console.log(obj);
// { '2': 2, foo: 'foo', bar: 'bar','[object Object]': 'someobj' }
Уведомление: Хотя это немного не по теме, вам нужно знать, как создатьMapОдной из причин структуры данных является возможность хранения ключей/значений в случаях, когда ключи не являются строками.
Какой символ?
Теперь, когда мы знаем, что такое основные типы данных, мы можем, наконец, определить символы. Символы — это примитивные типы данных, которые невозможно воссоздать. В этом случае Symbol похож на объект в том смысле, что создание нескольких экземпляров объекта также приведет к получению не совсем равных значений. Однако Symbol также является примитивным типом данных, поскольку его нельзя изменить. Вот пример использования символа:
const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2); // false
При создании экземпляра значения символа существует необязательный параметр предпочтения, которому вы можете назначить строку. Это значение используется для отладки кода и не влияет на сам символ.
const s1 = Symbol('debug');
const str = 'debug';
const s2 = Symbol('xxyy');
console.log(s1 === str); // false
console.log(s1 === s2); // false
console.log(s1); // Symbol(debug)
Символ как свойство объекта
Есть еще одно важное применение символов, их можно использовать как ключи в объектах! Вот пример использования символа в качестве ключа в объекте:
const obj = {};
const sym = Symbol();
obj[sym] = 'foo';
obj.bar = 'bar';
console.log(obj); // { bar: 'bar' }
console.log(sym in obj); // true
console.log(obj[sym]); // foo
console.log(Object.keys(obj)); // ['bar']
Обратите внимание, что клавиша символов неObject.keys()вернуть. Это также для обратной совместимости. В старых версиях JavaScript нет символьного типа данных, поэтому не следуетObject.keys()метод возвращается.
На первый взгляд кажется, что символы можно использовать для создания частных свойств объектов! В то время как многие другие языки программирования могут иметь приватные свойства в своих классах, отсутствие этой возможности в JavaScript долгое время считалось недостатком его синтаксиса.
К сожалению, код, взаимодействующий с объектом, по-прежнему может обращаться к тем свойствам объекта, ключами которых являются символы. даже вызов самого коданевозможноЭто также может произойти при доступе к символам. Например,Reflect.ownKeys()метод получения объектавсеСписок ключей, включая строки и символы:
function tryToAddPrivate(obj) {
obj[Symbol('Pseudo Private')] = 42;
}
const obj = { prop: 'hello' };
tryToAddPrivate(obj);
console.log(Reflect.ownKeys(obj));
console.log(obj[Reflect.ownKeys(obj)[1]]); // 42
Уведомление: В настоящее время ведется работа по добавлению закрытых свойств к классам в JavaScript. Эта функцияPrivate FieldsХотя это будет неправильновсеОбъекты хороши, но они хороши для объектов экземпляров класса. Частные поля доступны начиная с Chrome 74.
Предотвращение конфликтов имен свойств
Тип Symbol может быть плохим для получения частных свойств объектов в JavaScript. Еще одна причина, по которой они полезны, заключается в том, что символы позволяют избежать риска конфликта имен, когда разные библиотеки хотят добавить свойства к объектам.
Если есть две разные библиотеки, которые хотят прикрепить какие-то метаданные к объекту, обе могут захотеть установить какой-то идентификатор для объекта. использовать только два типа строкidПоскольку ключ идентифицирован, риск того, что несколько библиотек будут использовать один и тот же ключ, высок.
function lib1tag(obj) {
obj.id = 42;
}
function lib2tag(obj) {
obj.id = 369;
}
Применяя символы, каждая библиотека может генерировать нужные ей символы, создавая экземпляр класса Symbol. Затем в любой момент вы можете проверить и назначить значение ключа, соответствующее символам на соответствующем объекте.
const library1property = Symbol('lib1');
function lib1tag(obj) {
obj[library1property] = 42;
}
const library2property = Symbol('lib2');
function lib2tag(obj) {
obj[library2property] = 369;
}
По этой причине символыВ самом делеХорошо для JavaScript.
Однако вы можете задаться вопросом, почему каждая библиотека не может просто генерировать случайную строку при создании экземпляра или использовать специальное пространство имен?
const library1property = uuid(); // 随机方法
function lib1tag(obj) {
obj[library1property] = 42;
}
const library2property = 'LIB2-NAMESPACE-id'; // namespaced approach
function lib2tag(obj) {
obj[library2property] = 369;
}
Вероятно, вы правы, два описанных выше метода очень похожи на использование символов. Если обе библиотеки не используют одно и то же имя свойства, риск конфликта отсутствует.
Здесь проницательный читатель заметит, что эти два подхода не совсем одинаковы. Имена свойств с уникальными именами по-прежнему имеют недостаток: их ключи очень легко найти, особенно при запуске кода для перебора ключей или иной сериализации объектов. Рассмотрим следующий пример:
const library2property = 'LIB2-NAMESPACE-id'; // namespaced
function lib2tag(obj) {
obj[library2property] = 369;
}
const user = {
name: 'Thomas Hunter II',
age: 32
};
lib2tag(user);
JSON.stringify(user);
// '{"name":"Thomas Hunter II","age":32,"LIB2-NAMESPACE-id":369}'
Если мы используем символ для имени свойства объекта, вывод JSON не будет содержать значение, соответствующее символу. Почему это происходит? Тот факт, что JavaScript поддерживает символы, не означает, что спецификация JSON тоже изменилась! JSON допускает только строки в качестве ключей, и JavaScript не будет пытаться отображать свойства символов в окончательной полезной нагрузке JSON.
Мы можем использоватьobject.defineproperty(), который легко решает проблему строк объектов библиотеки, загрязняющих вывод JSON:
const library2property = uuid(); // namespaced approach
function lib2tag(obj) {
Object.defineProperty(obj, library2property, {
enumerable: false,
value: 369
});
}
const user = {
name: 'Thomas Hunter II',
age: 32
};
lib2tag(user);
// '{"name":"Thomas Hunter II","age":32,"f468c902-26ed-4b2e-81d6-5775ae7eec5d":369}
console.log(JSON.stringify(user));
console.log(user[library2property]); // 369
Путем преобразования перечисляемого строкового ключаДескрипторУстановите значение false, чтобы «скрыть» строковые клавиши, чтобы они вели себя так же, как символьные клавиши. они проходятObject.keys()Traversal тоже этого не видит, но это можно сделать с помощьюReflect.ownKeys()отображается следующим образом:
const obj = {};
obj[Symbol()] = 1;
Object.defineProperty(obj, 'foo', {
enumberable: false,
value: 2
});
console.log(Object.keys(obj)); // []
console.log(Reflect.ownKeys(obj)); // [ 'foo', Symbol() ]
console.log(JSON.stringify(obj)); // {}
В этот момент мыпочтиВоссозданные символы. Скрытые свойства строки и символы скрыты от сериализатора. Оба свойства можно использоватьReflect.ownKeys()метод извлечения, так что на самом деле он не является частным. Предполагая, что мы используем какое-то пространство имен/случайное значение для строковых свойств, мы устраняем риск случайных конфликтов имен для нескольких библиотек.
Однако небольшая разница все же есть. Поскольку строки неизменяемы, а символы всегда гарантированно уникальны, по-прежнему возможно генерировать идентичные строки и создавать конфликты. Математически это означает, что символы дают преимущества, которых мы не можем получить от строк.
В Node.js при проверке объекта (например, при использованииconsole.log()), если объект с именемinspectметод, вызывается функция, и выходные данные представляются в виде журнала объекта. Как вы можете себе представить, это поведение не то, что все ожидают, и его часто называютinspectметоды часто конфликтуют с объектами, созданными пользователями. Теперь для реализации этой функциональности доступны символы, которые можно использовать в require('util').inspection.custom .inspectМетоды устарели в Node.js v10 и полностью проигнорированы в v11. Теперь никто не изменит случайно поведение inspect!
Имитация частной собственности
Вот интересный метод, который мы можем использовать для моделирования частных свойств объектов. Этот подход использует другую функцию JavaScript: прокси. Прокси по существу инкапсулирует объект и позволяет нам по-разному взаимодействовать с этим объектом.
Прокси предоставляет ряд методов для перехвата операций, выполняемых над объектами. Нас интересует, что делает прокси при попытке прочитать ключ объекта. Я не буду вдаваться в подробности о том, как работают прокси, если вы хотите узнать больше, ознакомьтесь с другой нашей статьей:JavaScript Object Property Descriptors, Proxies, and Preventing Extension.
Мы можем использовать прокси, чтобы лгать о свойствах, доступных для объекта. В этом примере мы создадим прокси, который скрывает два наших известных скрытых свойства, одно из которых представляет собой строку_favColor, другой назначаетсяfavBookсимвол:
let proxy;
{
const favBook = Symbol('fav book');
const obj = {
name: 'Thomas Hunter II',
age: 32,
_favColor: 'blue',
[favBook]: 'Metro 2033',
[Symbol('visible')]: 'foo'
};
const handler = {
ownKeys: (target) => {
const reportedKeys = [];
const actualKeys = Reflect.ownKeys(target);
for (const key of actualKeys) {
if (key === favBook || key === '_favColor') {
continue;
}
reportedKeys.push(key);
}
return reportedKeys;
}
};
proxy = new Proxy(obj, handler);
}
console.log(Object.keys(proxy)); // [ 'name', 'age' ]
console.log(Reflect.ownKeys(proxy)); // [ 'name', 'age', Symbol(visible) ]
console.log(Object.getOwnPropertyNames(proxy)); // [ 'name', 'age' ]
console.log(Object.getOwnPropertySymbols(proxy)); // [Symbol(visible)]
console.log(proxy._favColor); // 'blue'
использовать_favColorСтроки просты: достаточно прочитать исходный код библиотеки. Кроме того, динамические клавиши могут (например,uuidпример) можно найти методом перебора. Однако без прямой ссылки на символ никто не можетproxyзначение доступно в объектеmetro 2033.
Декларация Node.js: функция Node.js, нарушающая конфиденциальность прокси. Эта функция не существует ни в самом языке JavaScript, ни в других случаях, таких как веб-браузеры. Эта функция позволяет получить доступ к базовому объекту при наличии прокси. Вот пример использования этой функции для уничтожения вышеуказанных частных свойств:
const [originalObject] = process
.binding('util')
.getProxyDetails(proxy);
const allKeys = Reflect.ownKeys(originalObject);
console.log(allKeys[3]); // Symbol(fav book)
Теперь нам нужно изменить глобальныйReflectвозражать или изменятьutilОбрабатывайте привязки, чтобы предотвратить их использование в конкретном экземпляре node.js. Но это дверь в новый мир, и если вы хотите понять его тайны, загляните в другие наши блоги:Protecting your JavaScript APIs.
Эта статья была написана мной и Томасом Хантером II. Я в компании под названиемIntricsicкомпании работают на (кстати, мыПрием на работу!), программное обеспечение, написанное специально для защиты приложений Node.js. В настоящее время у нас есть продукт, который применяет модель наименьших привилегий для защиты приложения. Наш продукт активно защищает приложения Node.js от злоумышленников, и его очень легко сделать. Если вы ищете способы защитить свое приложение Node.js, перейдите на страницуhello@inherin.comСвяжитесь с нами по.
автор фото баннераChunlea Ju
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.