Українська

Дізнайтеся про символи в JavaScript: їхнє призначення, створення, застосування для унікальних ключів властивостей, зберігання метаданих та уникнення конфліктів імен. Включено практичні приклади.

Символи в JavaScript: унікальні ключі властивостей та метадані

Символи в JavaScript, представлені в ECMAScript 2015 (ES6), надають механізм для створення унікальних та незмінних ключів властивостей. На відміну від рядків або чисел, символи гарантовано є унікальними в межах усього вашого JavaScript-застосунку. Вони дозволяють уникати конфліктів імен, додавати метадані до об'єктів, не втручаючись у наявні властивості, та налаштовувати поведінку об'єктів. Ця стаття надає комплексний огляд символів у JavaScript, охоплюючи їх створення, застосування та найкращі практики.

Що таке символи в JavaScript?

Символ — це примітивний тип даних у JavaScript, подібний до чисел, рядків, логічних значень, null та undefined. Однак, на відміну від інших примітивних типів, символи є унікальними. Кожного разу, коли ви створюєте символ, ви отримуєте абсолютно нове, унікальне значення. Ця унікальність робить символи ідеальними для:

Створення символів

Ви створюєте символ за допомогою конструктора Symbol(). Важливо зазначити, що ви не можете використовувати new Symbol(); символи — це не об'єкти, а примітивні значення.

Базове створення символу

Найпростіший спосіб створити символ:

const mySymbol = Symbol();
console.log(typeof mySymbol); // Вивід: symbol

Кожен виклик Symbol() генерує нове, унікальне значення:

const symbol1 = Symbol();
const symbol2 = Symbol();
console.log(symbol1 === symbol2); // Вивід: false

Описи символів

При створенні символу ви можете надати необов'язковий рядковий опис. Цей опис корисний для налагодження та логування, але він не впливає на унікальність символу.

const mySymbol = Symbol("myDescription");
console.log(mySymbol.toString()); // Вивід: Symbol(myDescription)

Опис є суто інформаційним; два символи з однаковим описом все одно залишаються унікальними:

const symbolA = Symbol("same description");
const symbolB = Symbol("same description");
console.log(symbolA === symbolB); // Вивід: false

Використання символів як ключів властивостей

Символи особливо корисні як ключі властивостей, оскільки вони гарантують унікальність, запобігаючи конфліктам імен при додаванні властивостей до об'єктів.

Додавання символьних властивостей

Ви можете використовувати символи як ключі властивостей так само, як рядки або числа:

const mySymbol = Symbol("myKey");
const myObject = {};

myObject[mySymbol] = "Hello, Symbol!";

console.log(myObject[mySymbol]); // Вивід: Hello, Symbol!

Уникнення конфліктів імен

Уявіть, що ви працюєте зі сторонньою бібліотекою, яка додає властивості до об'єктів. Ви можете захотіти додати власні властивості, не ризикуючи перезаписати наявні. Символи надають безпечний спосіб це зробити:

// Стороння бібліотека (симуляція)
const libraryObject = {
  name: "Library Object",
  version: "1.0"
};

// Ваш код
const mySecretKey = Symbol("mySecret");
libraryObject[mySecretKey] = "Top Secret Information";

console.log(libraryObject.name); // Вивід: Library Object
console.log(libraryObject[mySecretKey]); // Вивід: Top Secret Information

У цьому прикладі mySecretKey гарантує, що ваша властивість не конфліктуватиме з будь-якими наявними властивостями в libraryObject.

Перебір символьних властивостей

Однією з ключових характеристик символьних властивостей є те, що вони приховані від стандартних методів перебору, таких як цикли for...in та Object.keys(). Це допомагає захистити цілісність об'єктів та запобігає випадковому доступу або зміні символьних властивостей.

const mySymbol = Symbol("myKey");
const myObject = {
  name: "My Object",
  [mySymbol]: "Symbol Value"
};

console.log(Object.keys(myObject)); // Вивід: ["name"]

for (let key in myObject) {
  console.log(key); // Вивід: name
}

Щоб отримати доступ до символьних властивостей, потрібно використовувати Object.getOwnPropertySymbols(), який повертає масив усіх символьних властивостей об'єкта:

const mySymbol = Symbol("myKey");
const myObject = {
  name: "My Object",
  [mySymbol]: "Symbol Value"
};

const symbolKeys = Object.getOwnPropertySymbols(myObject);
console.log(symbolKeys); // Вивід: [Symbol(myKey)]
console.log(myObject[symbolKeys[0]]); // Вивід: Symbol Value

Відомі символи

JavaScript надає набір вбудованих символів, відомих як відомі символи, які представляють специфічну поведінку або функціональність. Ці символи є властивостями конструктора Symbol (наприклад, Symbol.iterator, Symbol.toStringTag). Вони дозволяють налаштовувати поведінку об'єктів у різних контекстах.

Symbol.iterator

Symbol.iterator — це символ, який визначає ітератор за замовчуванням для об'єкта. Коли об'єкт має метод з ключем Symbol.iterator, він стає ітерованим, що означає, що ви можете використовувати його з циклами for...of та оператором розширення (...).

Приклад: Створення власного ітерованого об'єкта

const myCollection = {
  items: [1, 2, 3, 4, 5],
  [Symbol.iterator]: function* () {
    for (let item of this.items) {
      yield item;
    }
  }
};

for (let item of myCollection) {
  console.log(item); // Вивід: 1, 2, 3, 4, 5
}

console.log([...myCollection]); // Вивід: [1, 2, 3, 4, 5]

У цьому прикладі myCollection є об'єктом, що реалізує протокол ітератора за допомогою Symbol.iterator. Функція-генератор повертає кожен елемент з масиву items, роблячи myCollection ітерованим.

Symbol.toStringTag

Symbol.toStringTag — це символ, який дозволяє налаштувати рядкове представлення об'єкта при виклику Object.prototype.toString().

Приклад: Налаштування представлення toString()

class MyClass {
  get [Symbol.toStringTag]() {
    return 'MyClassInstance';
  }
}

const instance = new MyClass();
console.log(Object.prototype.toString.call(instance)); // Вивід: [object MyClassInstance]

Без Symbol.toStringTag вивід був би [object Object]. Цей символ надає спосіб дати більш описове рядкове представлення ваших об'єктів.

Symbol.hasInstance

Symbol.hasInstance — це символ, який дозволяє налаштувати поведінку оператора instanceof. Зазвичай instanceof перевіряє, чи містить ланцюжок прототипів об'єкта властивість prototype конструктора. Symbol.hasInstance дозволяє перевизначити цю поведінку.

Приклад: Налаштування перевірки instanceof

class MyClass {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}

console.log([] instanceof MyClass); // Вивід: true
console.log({} instanceof MyClass); // Вивід: false

У цьому прикладі метод Symbol.hasInstance перевіряє, чи є екземпляр масивом. Це фактично змушує MyClass діяти як перевірка на масиви, незалежно від фактичного ланцюжка прототипів.

Інші відомі символи

JavaScript визначає кілька інших відомих символів, зокрема:

Глобальний реєстр символів

Іноді вам потрібно спільно використовувати символи в різних частинах вашого застосунку або навіть між різними застосунками. Глобальний реєстр символів надає механізм для реєстрації та отримання символів за ключем.

Symbol.for(key)

Метод Symbol.for(key) перевіряє, чи існує в глобальному реєстрі символ із заданим ключем. Якщо він існує, метод повертає цей символ. Якщо ні, він створює новий символ із цим ключем і реєструє його в реєстрі.

const globalSymbol1 = Symbol.for("myGlobalSymbol");
const globalSymbol2 = Symbol.for("myGlobalSymbol");

console.log(globalSymbol1 === globalSymbol2); // Вивід: true
console.log(Symbol.keyFor(globalSymbol1)); // Вивід: myGlobalSymbol

Symbol.keyFor(symbol)

Метод Symbol.keyFor(symbol) повертає ключ, пов'язаний із символом у глобальному реєстрі. Якщо символ не знаходиться в реєстрі, він повертає undefined.

const mySymbol = Symbol("localSymbol");
console.log(Symbol.keyFor(mySymbol)); // Вивід: undefined

const globalSymbol = Symbol.for("myGlobalSymbol");
console.log(Symbol.keyFor(globalSymbol)); // Вивід: myGlobalSymbol

Важливо: Символи, створені за допомогою Symbol(), *не* реєструються автоматично в глобальному реєстрі. Лише символи, створені (або отримані) за допомогою Symbol.for(), є частиною реєстру.

Практичні приклади та сценарії використання

Ось кілька практичних прикладів, що демонструють, як символи можна використовувати в реальних сценаріях:

1. Створення систем плагінів

Символи можна використовувати для створення систем плагінів, де різні модулі можуть розширювати функціональність основного об'єкта, не конфліктуючи з властивостями один одного.

// Основний об'єкт
const coreObject = {
  name: "Core Object",
  version: "1.0"
};

// Плагін 1
const plugin1Key = Symbol("plugin1");
coreObject[plugin1Key] = {
  description: "Plugin 1 adds extra functionality",
  activate: function() {
    console.log("Plugin 1 activated");
  }
};

// Плагін 2
const plugin2Key = Symbol("plugin2");
coreObject[plugin2Key] = {
  author: "Another Developer",
  init: function() {
    console.log("Plugin 2 initialized");
  }
};

// Доступ до плагінів
console.log(coreObject[plugin1Key].description); // Вивід: Plugin 1 adds extra functionality
coreObject[plugin2Key].init(); // Вивід: Plugin 2 initialized

У цьому прикладі кожен плагін використовує унікальний символьний ключ, запобігаючи потенційним конфліктам імен і гарантуючи, що плагіни можуть мирно співіснувати.

2. Додавання метаданих до DOM-елементів

Символи можна використовувати для прикріплення метаданих до DOM-елементів, не втручаючись у їхні наявні атрибути або властивості.

const element = document.createElement("div");

const dataKey = Symbol("elementData");
element[dataKey] = {
  type: "widget",
  config: {},
  timestamp: Date.now()
};

// Доступ до метаданих
console.log(element[dataKey].type); // Вивід: widget

Такий підхід зберігає метадані окремо від стандартних атрибутів елемента, покращуючи підтримку та уникаючи потенційних конфліктів із CSS або іншим кодом JavaScript.

3. Реалізація приватних властивостей

Хоча JavaScript не має справжніх приватних властивостей, символи можна використовувати для симуляції приватності. Використовуючи символ як ключ властивості, ви можете ускладнити (але не унеможливити) доступ до властивості для зовнішнього коду.

class MyClass {
  #privateSymbol = Symbol("privateData"); // Примітка: синтаксис '#' — це *справжнє* приватне поле, введене в ES2020, відмінне від прикладу

  constructor(data) {
    this[this.#privateSymbol] = data;
  }

  getData() {
    return this[this.#privateSymbol];
  }
}

const myInstance = new MyClass("Sensitive Information");
console.log(myInstance.getData()); // Вивід: Sensitive Information

// Доступ до "приватної" властивості (складно, але можливо)
const symbolKeys = Object.getOwnPropertySymbols(myInstance);
console.log(myInstance[symbolKeys[0]]); // Вивід: Sensitive Information

Хоча Object.getOwnPropertySymbols() все ще може розкрити символ, це робить менш імовірним випадковий доступ або зміну "приватної" властивості зовнішнім кодом. Примітка: Справжні приватні поля (з використанням префікса `#`) тепер доступні в сучасному JavaScript і пропонують сильніші гарантії приватності.

Найкращі практики використання символів

Ось кілька найкращих практик, про які варто пам'ятати при роботі з символами:

Висновок

Символи в JavaScript пропонують потужний механізм для створення унікальних ключів властивостей, прикріплення метаданих до об'єктів та налаштування поведінки об'єктів. Розуміючи, як працюють символи, та дотримуючись найкращих практик, ви можете писати більш надійний, підтримуваний та вільний від колізій код на JavaScript. Незалежно від того, чи ви створюєте системи плагінів, додаєте метадані до DOM-елементів або симулюєте приватні властивості, символи є цінним інструментом для покращення вашого процесу розробки на JavaScript.

Символи в JavaScript: унікальні ключі властивостей та метадані | MLOG