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

let symbol1 = Symbol();
let symbol2 = Symbol();
console.log(symbol1 === symbol2) // false

Для отладки мы также можем предоставить описание символа, передав его в качестве аргумента. Несколько символов могут иметь одно и то же описание, но они все равно не будут одинаковыми. Мы можем проверить описание символа, используя свойство description, присутствующее в символе.

let symbol1 = Symbol('my-description');
let symbol2 = Symbol('my-description');
console.log(symbol1 === symbol2) // false
console.log(symbol1.description) // "my-description"

Символы как «скрытые» свойства

Символы обычно используются для создания свойств, которые должны быть скрыты и не могут быть напрямую доступны или перезаписаны. Ключи свойств в объектах могут быть двух типов: либо string, либо symbol. Мы можем использовать символы для создания скрытых свойств. Например:

function fn1() {
  const databaseName = Symbol('databaseName');
  const databasePassword = Symbol('databasePassword')
  
  const userSecretInfo = {
    password: 'password', // String type key
    [databaseName]: 'databaseName', // Symbol type key
    [databasePassword]: 'databasePassword' // Symbol type key
  }
  console.log(userSecretInfo['databaseName']) // undefined
  console.log(userSecretInfo[databasePassword]) // "databasePassword"
  fn2(userSecretInfo) // Forgot to remove the critical passwords from the object and directly passed object in another function
}
function fn2(info) {
   console.log(info) // Now there is no more way to access the Symbol properties in here
}
fn1()

В приведенном выше примере мы создали 2 функции, fn1 и fn2. В fn1 мы создали два символа и назначили их в качестве свойств в userSecretInfo, который также содержит password как свойство строкового типа.

Когда userSecretInfo печатается в fn1, регистрируются все свойства внутри объекта. Но, как только мы попытаемся зарегистрировать userSecretInfo[‘databaseName’], мы получим undefined в качестве вывода, потому что значение типа символа недоступно напрямую, к ним можно получить доступ только с помощью переменных символа, как показано в операторе console.log(userSecretInfo[databasePassword]).

Кроме того, как только этот объект будет передан какой-либо другой функции, у них больше не будет возможности получить доступ к скрытым свойствам, поскольку область действия переменных символа была ограничена функцией fn1.

Символы не повторяются в циклах for…in

Символические свойства не повторяются в for…in циклах. Они остаются скрытыми и недоступными на протяжении всего процесса итерации. Наряду с циклами for…in символы также не повторяются при использовании Object.keys(). Мы получим ключи только для свойств строкового типа.

const symbolKey3 = Symbol();
const myTestObj = {
  stringKey1: 'value1',
  stringKey2: 'value2',
  [symbolKey3]: 'value3'
}
for( let key in myTestObj ) {
  console.log(myTestObj[key]) // "value1", "value2"
}
Object.keys(myTestObj) // [ "stringKey1", "stringKey2" ]

Однако при использовании Object.assign() символы по-прежнему клонируются. Например:

const clone = Object.assign({}, myTestObj);
console.log(clone[symbolKey3]) // "value3"

Глобальные символы

Как мы уже видели, символы используются для создания уникальных значений, но могут быть определенные сценарии, в которых мы можем захотеть каким-то образом сохранить символ глобально, чтобы использовать его позже в некоторых других частях кода.

Мы можем добиться этого с помощью Global Symbol Registry, в котором мы можем создавать символы и обращаться к ним позже. Делается это с помощью метода Symbol.for(key).

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

// Key does not exist in symbol registry, a new symbol get registered with 'databaseName' key
let databaseName = Symbol.for('databaseName');
// Reading 'databaseName' key from some other part of code, which will return same registered symbol
let newDatabaseName = Symbol.for('databaseName');
console.log(databseName === newDatabaseName) // true

Теперь мы знаем, как получить зарегистрированный символ с помощью ключа, но мы также можем сделать обратное, где мы можем запросить имя ключа, с которым регистрируется символ. Это делается с помощью Symbol.keyFor(sym), где sym означает символ.

let databseName = Symbol.for('databaseName')
let newDatabaseName = Symbol.for('databaseName');
console.log(Symbol.keyFor(newDatabaseName)) // "databaseName"

Системные или общеизвестные символы

Это те глобальные символы, которые встроены в JavaScript для предоставления различных свойств объектам. Некоторые из них перечислены ниже: -

  • Symbol.toPrimitive
  • Symbol.hasInstance
  • Symbol.iterator
  • Symbol.isConcatSpreadable
  • … и так далее.

Заключение

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