Это первая часть про символы и их использование в 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
), также посмотрим на возможные примеры использования символа.
ссылка на оригинал статьи http://habrahabr.ru/post/255137/
Добавить комментарий