Шпаргалка по JavaScript Symbol-ам

Що таке Symbol в JavaScript

Symbol — один з примітивних типів даних. Це дещо своєрідне та може бути складнішим в розумінні.

Виклик `Symbol()` створює новий символ, значення якого гарантовано відрізняється від будь-яких створений раніше символів.

const sym1 = Symbol();
const sym2 = Symbol();
sym1 === sym2; // false

Що ж це за значення? Це не текст, не літера, не число. У символів немає якогось текстового представлення. Тому якщо ви спробуєте вивести символ в консоль, ви побачите лише “Symbol()” — факт того, що це символ, але не його “значення”, бо його заховано глибоко в реалізації рушія.

console.log( Symbol() ); // Symbol() 🤷‍♂️

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

function createObj() {
  const sym = Symbol()
  const obj = {
    [sym]: 'Привіт, Світе!'
  };

  console.log( obj[sym] ) // Привіт, Світе!

  return obj;
}

const obj2 = createObj();
console.log(obj) // { Symbol(): "Привіт, Світе!" }

Вивівши структуру obj2 ви можете побачити, що в ньому є властивість за ключем-символом, але ви не можете отримати доступ до цієї властивості, оскільки змінна-ключ залишилась в області видимості createObj().

Для чого вони потрібні

Оскільки символ це примітив, то його можна використовувати як ключ для властивостей об’єктів.

В моїй практиці при розробці бібліотеки я кілька разів опирався на символи, щоб домішувати щось в користувацькі об’єкти та гарантувати, що я не перезапишу якісь дані.

const cachedKey = Symbol()

export function doSomeJobWithObjectAndCacheResult(obj) {
  if (obj[cachedKey] === undefined) {
    obj[cachedKey] = doSomeJobWithObject(obj)
  }

  return obj[cachedKey]
}
  1. Оскільки кожен створений символ це унікальний примітив, то функція вище гарантує, що в переданому об’єкті не може бути поля за цим символом створеним десь окрім як цією функцією.

  2. Доступ до цього поля неможливий за межами цієї функції.

Які є вбудовані Symbol

В JavaScript чимало символів створюються та використовуються замовчуванням. Всі вони визначені у статичних властивостях Symbol.

console.dir(Symbol) /*
{
  name: "Symbol"
  asyncIterator: Symbol("Symbol.asyncIterator"),
  hasInstance: Symbol("Symbol.hasInstance"),
  isConcatSpreadable: Symbol("Symbol.isConcatSpreadable"),
  iterator: Symbol("Symbol.iterator"),
  match: Symbol("Symbol.match"),
  matchAll: Symbol("Symbol.matchAll"),
  replace: Symbol("Symbol.replace"),
  search: Symbol("Symbol.search"),
  species: Symbol("Symbol.species"),
  split: Symbol("Symbol.split"),
  toPrimitive: Symbol("Symbol.toPrimitive"),
  toStringTag: Symbol("Symbol.toStringTag"),
  unscopables: Symbol("Symbol.unscopables"),
}
*/

І ви їх використовуєте, навіть не усвідомлюючи цього. Наприклад, викликаючи цикл for на масиві:

const arr = [1,2,3]

for (const num of arr) {
  console.log(num)
}

// 1
// 2
// 3

У масивів є прихований метод, який вираховує та повертає значення для кожної ітерації (такі функції називаються ітераторами). А захований цей метод за символом Symbol.iterator.

Тобто, під час роботи циклу рушій JavaScript викликає прихований метод `arr[Symbol.iterator]()` та записує його результат в змінну num. І повторює цей процес доти, доки `arr[Symbol.iterator]()` повертає бодай щось.

І ми можемо змінювати поведінку JavaScript змінюючи приховані методи за такими символами:

const arr = [1,2,3]

arr[Symbol.iterator] = function* () {
  yield 'Що це?';
  yield 'Це взагалі законно?';
  yield 'Чортівня! 🪄';
} 

for (const num of arr) {
  console.log(num)
}

// Що це?
// Це взагалі законно?
// Чортівня! 🪄

Змінивши метод за Symbol.iterator ми фактично переписали реалізацію перебору значень масиву. І це працює не лише для циклів

const newArr = [...arr]
console.log(newArr)
// [ "Що це?", "Це взагалі законно?", "Чортівня! 🪄" ]

Шпаргалка по JavaScript Symbol-ам

Symbol.hasInstance

Метод, який визначає, чи розпізнає об’єкт-конструктор інший об’єкт як свій екземпляр. Використовується в `instanceof`.

obj instanceof Array // obj[Symbol.hasInstance](Array)

Symbol.isConcatSpreadable

Логічне значення, яке визначає, чи потрібно “розгортати” значення при конкатенації з масивом, чи залишати як є. Використовується в `Array.prototype.concat()`.

const arr1 = [1,2,3]
const arr2 = [4,5,6]

console.log(arr1.concat(arr2)) // [1,2,3,4,5,6]

arr2[Symbol.isConcatSpreadable] = false

console.log(arr1.concat(arr2)) // [1,2,3,[4,5,6]]

Symbol.iterator та Symbol.asyncIterator

Про цього написав трохи вище. Методи, які повертають ітератори об’єкта.

Symbol.match та Symbol.matchAll

Методи які використовуються в `String.prototype.match()` та `String.prototype.matchAll()` відповідно.

'foo'.match(obj)    // obj[Symbol.match]('foo')
'foo'.matchAll(obj) // obj[Symbol.matchAll]('foo')

Symbol.replace

Метод, який визначає як буде замінено частину рядка. Використовується в `String.prototype.replace()`.

'foo'.replace(obj, 'bar') // obj[Symbol.replace]('foo', 'bar')

Symbol.search

Метод який повертає індекс символу який відповідає об’єкту. Використовується в `String.prototype.search()`.

'foo'.search(obj) // obj[Symbol.search]('foo')

Symbol.species

Функція конструктор. Методи, що створюють копії об’єктів, можуть звертатись до цього символу, щоб змінювати прототип для новоствореної копії.

class MyArray extends Array {
  // Визначає, що всі копії будуть вважати Array за батьківський конструктор
  static get [Symbol.species]() {
    return Array;
  }
}
const a = new MyArray(1, 2, 3);
const mapped = a.map((x) => x * x);

console.log(a.constructor); // MyArray 
console.log(mapped.constructor); // Array

Symbol.split

Метод, який розрізає рядок за переданим регулярним виразом. Використовується в `String.prototype.split()`.

'foo'.split(obj, limit) // obj[Symbol.split]('foo', limit)

Symbol.toPrimitive

Метод, який конвертує об’єкт в примітив. Використовується при приведенні типів.

const obj = {};
console.log(+obj);     // NaN
console.log(`${obj}`); // "[object Object]"
console.log(obj + ""); // "[object Object]"

// Змінюємо алгоритм конвертації об'єкту в примітивні значення.
obj[Symbol.toPrimitive] = function(hint) {
  if (hint === "number") {
    return 42;
  }
  if (hint === "string") {
    return "Привіт";
  }
  return true;
};

console.log(+obj);     // 42       — hint === "number"
console.log(`${obj}`); // "Привіт" — hint === "string"
console.log(obj + ""); // "true"   — hint === "default"

Symbol.unscopables

Об’єкт, ключі якого буде виключено з області видимості with

const obj {
  name: 'Alex'
  password: '1234'

  [Symbol.unscopables]: {
    password: true
  }
}

with(obj) {
  console.log(name) // Alex
  console.log(password) // undefined
}

Підсумок

Як бачите, чимала частина “внутрішньої” поведінки JavaScript визначена в прихованих за символами методах. І хоч я й називаю їх прихованими, насправді ви вільні маніпулювати ними як завгодно, створювати власні унікальні класи з нестандартною поведінкою.

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Олександр Козак
Олександр Козак@kozack

4.6KПрочитань
1Автори
59Читачі
На Друкарні з 14 квітня

Більше від автора

  • Як зробити нескінченну прокрутку на сайті. Або 10 недоліків та одна перевага Infinity Scroll

    В цій статті я хочу описати 10 поширених проблем, пов’язаних з нескінченною прокруткою, які розробнику доведеться вирішувати при реалізації Infinity Scroll. Та про єдину причину, чому попри всі недоліки нескінченну прокрутку активно використовують.

    Теми цього довгочиту:

    Ui-ux
  • Чому ви повинні забути про rgb() та hex кольори при роботі з вебсайтами

    Формат задання кольорів функцією rgb() було представлено ще у 1996 році. Очевидно, що CSS далеко розвинувся з тих часів. Попри це, чомусь, багато веброзробників та вебдизайнерів вперто продовжують використовувати цей застарілий, обмежений та нелюдський формат кольору по цей день.

    Теми цього довгочиту:

    Css
  • Чому автоматичні імпорти будь-де це жахлива ідея 💩

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

    Теми цього довгочиту:

    Javascript

Вам також сподобається

Коментарі (3)

Класний матеріал, дякую! Особливо повеселилась на моменті, де можна переписати цикл for. Збираюсь тепер трохи потролити своїх джунів)))

Вам також сподобається