Symbol - примитивный тип данных, экземпляры которого уникальны и неизменяемы и могут быть использованы как ключ (идентификатор) для свойства объекта.
Примитивные значения иммутабельны: их нельзя изменять.
Symbol — это уникальное примитивное значение: создание нескольких экземпляров символов приведёт к созданию разных значений, например:
1 2 3 4 5 6 7 |
symbol_1 = Symbol("symbol"); symbol_2 = Symbol("symbol"); console.log(symbol_1 === symbol_2); // false console.log(symbol_1); // Symbol(symbol) console.log(symbol_2); // Symbol(symbol) |
При создании экземпляра Symbol можно воспользоваться необязательным первым аргументом (тип number или string), который представляет собой описание символа, предназначенное для использования при отладке (на сам Symbol это значение не влияет):
1 2 3 4 5 6 7 |
symbol_1 = Symbol("symbol_1"); symbol_2 = Symbol("symbol_2"); console.log(symbol_1 === symbol_2); // false console.log(symbol_1); // Symbol(symbol_1) console.log(symbol_2); // Symbol(symbol_2) |
Symbol как ключи (идентификаторы) свойств объектов
Символы можно использовать в качестве ключей свойств объектов, что позволяет получить уникальные идентификаторы для свойств объекта, например:
1 2 3 4 5 6 7 8 |
const object = {}; const symbol_1 = Symbol("key"); const symbol_2 = Symbol("key"); object[symbol_1] = "foo"; object[symbol_2] = "bar"; object.foobar = "foobar"; console.log(object); // {foobar: 'foobar', Symbol(key): 'foo', Symbol(key): 'bar'} |
Ключи, заданные с помощью Symbol, не возвращаются при вызове метода Object.keys(), но могут быть получены с помощью метода Reflect.ownKeys() (можно получить список всех ключей объекта, и тех, что являются строками, и тех, что являются Symbol):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
let user = { firstName: "Alex", isAdmin: true, "user-adress": { country: "Russia", sity: "Moscow" }, skills: ["html", "css", "JS"], getFoo: { value: function () { return this.foo; }, }, [Symbol()]: "Symbol()" }; console.log(Object.getOwnPropertyNames(user)); // (5) ['firstName', 'isAdmin', 'user-adress', 'skills', 'getFoo'] console.log(Object.keys(user)); // (5) ['firstName', 'isAdmin', 'user-adress', 'skills', 'getFoo'] // Reflect.ownKeys() выводит также и ключи Symbol console.log(Reflect.ownKeys(user)); // (6) ['firstName', 'isAdmin', 'user-adress', 'skills', 'getFoo', Symbol()] |
Ключи, заданные с помощью Symbol, не всегда обеспечивают полную приватность данных в объекте.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function addPseudoPrivate(object) { object[Symbol("Pseudo Private")] = "Not privat"; } const object = { prop: "My prop" }; addPseudoPrivate(object); console.log(object); // {prop: 'My prop', Symbol(Pseudo Private): 'Not privat'} console.log(object[Symbol("Pseudo Private")]); // undefined // можно получить доступ, обратившись как к элементу массива console.log(Reflect.ownKeys(object)); // [ 'prop', Symbol(Pseudo Private) ] console.log(object[Reflect.ownKeys(object)[1]]); // Not privat |
Ещё пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function User(name) { const symbol = Symbol(); return { [symbol]: name, getName() { return this[symbol]; }, setName(name) { this[symbol] = name; }, }; } const newUser = new User("Alex"); console.log(Reflect.ownKeys(newUser)); // (3) ['getName', 'setName', Symbol()] console.log(newUser[Symbol()]); // undefined console.log(newUser[Reflect.ownKeys(newUser)[2]]); // Alex (доступ к значению получен) |
Использование Symbol для предотвращения коллизий имён свойств объектов
Symbol позволяют добавлять в объекты свойства, описанные за пределами этих объектов, при этом не опасаясь коллизии (конфликта) ключей (идентификаторов).
Пример коллизии имен свойств объекта (функция перезаписывает поле объекта, созданное другой функцией):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
let object = {}; function add_1(object) { object.id = 10; } function add_2(object) { object.id = "20"; } add_1(object) console.log(object); // {id: 10} add_2(object) console.log(object); // {id: '20'} (свойство перезаписано) |
Если же воспользоваться при создании ключей свойств Symbol, то это позволит сгенерировать уникальные идентификаторы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
let object = {}; function add_1(object) { let id = Symbol("id"); object[id] = 10; } function add_2(object) { let id = Symbol("id"); object[id] = "20"; } add_1(object); console.log(object); // {Symbol(id): 10} add_2(object); // console.log(object); // {Symbol(id): 10, Symbol(id): '20'} (свойство добавлено, а не перезаписано) |
Symbol и JSON.Stringify()
JSON-представление объекта не содержит значения Symbol, т.к. новый тип данных JavaScript не внесен в спецификацию JSON (JSON поддерживает в качестве ключей свойств объектов только строки), например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
let object = {}; function add_1(object) { let id = Math.random().toFixed(3); // любая функция, возвращающая UUID или GUID (random() только для примера) object[id] = 10; } function add_2(object) { let id = Symbol("id"); object[id] = "20"; } add_1(object); console.log(object); // {0.650: 10} add_2(object); console.log(object); // {0.650: 10, Symbol(id): '20'} console.log(JSON.stringify(object)); // {"0.650":10} // Symbol(id) отсутствует |
* про UUID и GUID можно почитать, например, здесь.
Скрыть строковые представления имён свойств в JSON-представлении объекта можно, благодаря использованию Object.defineProperty():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
let object = {}; function add_1(object) { let id = Math.random().toFixed(3); // любая функция, возвращающая UUID или GUID Object.defineProperty(object, id, { value: 10, enumerable: false, }); } add_1(object); console.log(object); // {0.650: 10} console.log(JSON.stringify(object)); // {} |
Строковые ключи, «скрытые» благодаря установке их дескриптора enumerable в значение false, ведут себя практически так же, как и ключи, представленные Symbol:
- не выводятся при вызове Object.keys();
- их можно обнаружить, воспользовавшись Reflect.ownKeys() (их нельзя назвать по-настоящему приватными).
Имитация приватных свойств с помощью Proxy и Symbol
См. также Прокси (developer.mozilla.org) и Proxy и Reflect (learn.javascript.ru)
Объект Proxy позволяет создать прокси для другого объекта, может перехватывать и переопределить основные операции для данного объекта. Т.е. прокси-объекты служат обёртками для других объектов и позволяют вмешиваться в действия, выполняемые с этими объектами.
Прокси - это новые объекты: невозможно выполнить "проксирование" существующего объекта.
Синтаксис создания прокси:
var p = new Proxy(target, handler);
где:
- target - исходный объект (может быть объектом любого типа, включая массив, функцию и даже другой прокси объект);
- handler - объект-обработчик, методы (ловушки) которого определяют поведение прокси во время выполнения операции над ним.
Прокси-объект предоставляет возможность управления операциями чтения ключей объекта., т.е., например, мы можем использовать прокси для управления видимостью свойств объекта извне.
Пример отсюда Особенности использования типа данных Symbol в JavaScript
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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) => { // метод-"ловушка" объекта Proxy const reportedKeys = []; const actualKeys = Reflect.ownKeys(target); // получаем все ключи объекта target for (const key of actualKeys) { // перебираем ключи if (key === favBook || key === "_favColor") { // если идентификаторы совпадают с заданными - пропускаем continue; } reportedKeys.push(key); // если идентификаторы НЕ совпадают с заданными - добавляем в итоговый массив ключей } return reportedKeys; // выводим итоговый массив ключей }, }; proxy = new Proxy(obj, handler); } console.log(proxy); // Proxy {name: 'Thomas Hunter II', age: 32, _favColor: 'blue', Symbol(fav book): 'Metro 2033', Symbol(visible): 'foo'} // пытаемся получить список свойств объекта различными методами 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 console.log(proxy[Reflect.ownKeys(proxy)[3]]); // undefined |