
Exploring JavaScript Symbols. Symbol — новый тип данных в JavaScript
- Tutorial
Это первая часть про символы и их использование в JavaScript.
Новая спецификация ECMAScript (ES6) вводит дополнительный тип данных — символ (symbol). Он пополнит список уже доступных примитивных типов (string, number, boolean, null, undefined). Интересной особенностью символа по сравнению с остальными примитивными типами является то, что он единственный тип у которого нет литерала.
Для чего же нужен был дополнительный тип данных?
В JavaScript нет возможности объявить свойство объекта как приватное. Чтобы скрыть данные можно использовать замыкания, но тогда все свойства нужно объявлять в конструкторе (так как нет возможности объявить их в прототипе), к тому же они будут создаваться для каждого экземпляра, что увеличит размер используемой памяти. ECMAScript 5 предоставил возможность указать
Такая конструкция объявления всё равно не лишает возможности получить значение свойства, если напрямую обратиться к нему:
В других языках, к примеру, можно добавить модификатор метода, чтобы определить его видимость (protected, private, public). Но в новой спецификации JavaScript выбрали другой подход и решили не вводить модификаторы, а определять поведение в зависимости от типа идентификатора свойства. Раньше имя свойства было строкой, теперь же это может быть как строка так и символ. Такой подход позволяет не менять саму концепцию объявления объектов:
В данном примере объявлен объект
Свойство
В данном случае будут вычислены все три выражения и их результаты будут именами свойств. Возможность использовать динамические (получаемые в результате вычисления выражения) имена свойств для литералов объекта добавлены в ES6.
Ключевой особенностью символа, которой он отличается от строки, является то, что обратиться к свойству которое объявлено через символ можно только по ссылке на данный символ. К примеру, eсли у объекта
Получить роль пользователя таким образом мы не можем:
Для того, чтобы получить роль, нужно обращаться к свойству по ссылке на символ:
Свойство объявленное через символ не будет видно в
Рассмотрим особенности символов.
Как уже было показано в примере выше, чтобы создать символ нужно вызвать функцию
Функция
Описание символа служит только для того, чтобы помочь при отладке, оно не изменяет поведение символа и обратиться к символу через описание нельзя, также нет метода, чтобы получить или изменить описание символа.
Спецификация ES6 больше не поддерживает явное создание объектов примитивов, поэтому следующая конструкция выбросит ошибку:
В целях обратной совместимости для
Важной особенностью символа также является то, что его значение уникально:
Это поведение открывает перед нами больше возможностей при работе с объектами, например, несколько модулей могут расширять объект новыми свойствами, не беспокоясь за возможные конфликты имен.
Для определения символа можно использовать
В текущей системе приведения типов JavaScript есть много нюансов и символы добавляют еще одну особенность тем, что в отличии от остальных примитивных значений символ нельзя преобразовать к строке или числу. При попытке преобразовать к числу или строке будет выброшена ошибка
В данном примере не однозначно, что должно быть в результате сохранено в переменную
Чтобы не было такой неоднозначности, и было выбрано поведение, что при попытке преобразовать символ будет ошибка.
Это основная информация о символах, как о типе данных. В следующей части продолжим рассматривать символ и изучать методы символа (как создать глобальный символ, как работет
Новая спецификация ECMAScript (ES6) вводит дополнительный тип данных — символ (symbol). Он пополнит список уже доступных примитивных типов (string, number, boolean, null, undefined). Интересной особенностью символа по сравнению с остальными примитивными типами является то, что он единственный тип у которого нет литерала.
Для чего же нужен был дополнительный тип данных?
В JavaScript нет возможности объявить свойство объекта как приватное. Чтобы скрыть данные можно использовать замыкания, но тогда все свойства нужно объявлять в конструкторе (так как нет возможности объявить их в прототипе), к тому же они будут создаваться для каждого экземпляра, что увеличит размер используемой памяти. ECMAScript 5 предоставил возможность указать
enumerable: false
для свойства, что позволяет скрыть свойство от перечисления в for-in
и его не будет видно в Object.keys
, но для этого нужно объявлять его через конструкцию Object.defineProperty
. var user = {};
Object.defineProperty( user, 'role', {
enumerable: false,
value: 'admin'
});
Такая конструкция объявления всё равно не лишает возможности получить значение свойства, если напрямую обратиться к нему:
var userRole = user.role; // 'admin'
В других языках, к примеру, можно добавить модификатор метода, чтобы определить его видимость (protected, private, public). Но в новой спецификации JavaScript выбрали другой подход и решили не вводить модификаторы, а определять поведение в зависимости от типа идентификатора свойства. Раньше имя свойства было строкой, теперь же это может быть как строка так и символ. Такой подход позволяет не менять саму концепцию объявления объектов:
var role = Symbol();
var user = {
id: 1001,
name: 'Administrator',
[role]: 'admin'
};
В данном примере объявлен объект
user
у которого два свойства объявлены через строковые идентификаторы (id
, name
) и одно свойство через символ (role
).Свойство
role
объявлено в квадратных скобках, чтобы оно не интерпретировалось как строка, а было получено в результате вычисления выражения. Данный объект можно также объявить следующим образом, чтобы лучше понять данную конструкцию: var role = Symbol();
var user = {
['id']: 1001,
['name']: 'Administrator',
[role]: 'admin'
};
В данном случае будут вычислены все три выражения и их результаты будут именами свойств. Возможность использовать динамические (получаемые в результате вычисления выражения) имена свойств для литералов объекта добавлены в ES6.
Ключевой особенностью символа, которой он отличается от строки, является то, что обратиться к свойству которое объявлено через символ можно только по ссылке на данный символ. К примеру, eсли у объекта
user
нужно получить имя пользователя нужно написать данный код: var userName = user.name; // 'Administrator'
// OR
var userName = user['name']; // 'Administrator'
Получить роль пользователя таким образом мы не можем:
var userRole = user.role; // undefined
// OR
var userRole = user['role']; // undefined
Для того, чтобы получить роль, нужно обращаться к свойству по ссылке на символ:
var role = Symbol();
var user = {
id: 1001,
name: 'Administrator',
[role]: 'admin'
};
var userRole = user[role]; // 'admin'
Свойство объявленное через символ не будет видно в
for-in
, Object.keys
, Object.getOwnPropertyNames
, также не будет добавлено при использовании JSON.stringify
.Рассмотрим особенности символов.
Как уже было показано в примере выше, чтобы создать символ нужно вызвать функцию
Symbol
: var score = Symbol();
Функция
Symbol
также принимает необязательный параметр — строку, которая служит для описания символа: var score = Symbol('user score');
console.log( score ); // Symbol(user score)
Описание символа служит только для того, чтобы помочь при отладке, оно не изменяет поведение символа и обратиться к символу через описание нельзя, также нет метода, чтобы получить или изменить описание символа.
Спецификация ES6 больше не поддерживает явное создание объектов примитивов, поэтому следующая конструкция выбросит ошибку:
var score = new Symbol('score'); // TypeError
В целях обратной совместимости для
String
, Number
и Boolean
— ошибка не будет выбрасываться (но лучше не использовать устарешнее поведение). Если нужно работать не с примитивом, а с его объектом можно воспользоваться функцией Object
передав ей примитив в качестве параметра: var symbol = Symbol('symbol');
var string = 'string';
var number = 5;
var symbolObj = Object( symbol );
var stringObj = Object( string );
var numberObj = Object( number );
console.log( symbol ); // Symbol(symbol)
console.log( string ); // 'string'
console.log( number ); // 5
console.log( symbolObj ); // Symbol {}
console.log( stringObj ); // String { 0: 's', 1: 't', 2: 'r', 3: 'i', 4: 'n', 5: 'g', length: 6, [[PrimitiveValue]]: 'string' }
console.log( numberObj ); // Number { [[PrimitiveValue]]: 5 }
Важной особенностью символа также является то, что его значение уникально:
var firstScore = Symbol('score');
var secondScore = Symbol('score');
firstScore === secondScore; // false
Это поведение открывает перед нами больше возможностей при работе с объектами, например, несколько модулей могут расширять объект новыми свойствами, не беспокоясь за возможные конфликты имен.
Для определения символа можно использовать
typeof
, в случае если значения является символом будет возвращена строка symbol
: function isSymbol( value ) {
return typeof value === 'symbol';
}
var firstScore = Symbol('score');
var secondScore = 'score';
isSymbol( firstScore ); // true
isSymbol( secondScore ); // false
В текущей системе приведения типов JavaScript есть много нюансов и символы добавляют еще одну особенность тем, что в отличии от остальных примитивных значений символ нельзя преобразовать к строке или числу. При попытке преобразовать к числу или строке будет выброшена ошибка
TypeError
. Такое поведение выбрано для того, чтобы случайно не создать строковое значение, которое в итоге будет использовано как имя свойства: var userObject = {};
var role = Symbol() + 'type';
var id = 10001;
userObject.id = id;
userObject[ role ] = 'admin';
В данном примере не однозначно, что должно быть в результате сохранено в переменную
role
, если строка, тогда свойство userObject[ role ] = 'admin'
будет объявлено через строку и к нему будет прямой доступ (но так как использовался символ, скорее всего было желание скрыть значение свойства). С другой стороны, если в результатом выражения будет символ, а так как получить значения символа нельзя, значит определить наличие в нем строки type
нельзя, и это уже не явное поведение и нужно информировать разработчика в ситуациях, когда он преднамеренно пытается создать строковое значение из символа, потому что такая конструкция не имеет смысла.Чтобы не было такой неоднозначности, и было выбрано поведение, что при попытке преобразовать символ будет ошибка.
Это основная информация о символах, как о типе данных. В следующей части продолжим рассматривать символ и изучать методы символа (как создать глобальный символ, как работет
Object.getOwnPropertySymbols
), также посмотрим на возможные примеры использования символа.
Comments 61
Only users with full accounts can post comments. Log in, please.