ES6 В ГЛИБИНІ: СИМВОЛИ

Оригінал: hacks.mozilla.org

https://secure.gravatar.com/avatar/5a83f46f9f97d184457cb2dedfa19173?s=64&d=mm&r=g

Джейсон Орендорф

Опубліковано 11 червня 2015 року в ES6 В ГлибиніВибрана Стаття, і JavaScript

ES6 В Глибині – це серія про нові функції, що додаються до мови програмування JavaScript в 6-му виданні стандарту ECMAScript, коротко ES6.

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

Символи – це не логотипи.

Це не маленькі малюнки, які ви можете використовувати у своєму коді.

Нехай  😻 = 😺 × 😍; // SyntaxError

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

Вони точно не те саме, що цимбали.

C:\Users\саша\Desktop\302555333_c3f827fd75_z-250x188.jpg

(Це не дуже гарна ідея використовувати цимбали в програмуванні. Вони мають тенденцію до збоїв.)

Отже, що це за символи?

Сьомий тип

Оскільки JavaScript вперше стандартизовані у 1997 році, їх було шість типів . До ES6 кожне значення програми JS не входило до однієї з цих категорій.

  • Не визначено
  • Нуль
  • Булева
  • Номер
  • Рядок
  • Об’єкт

Кожен тип – це набір значень. Перші п’ять наборів усі кінцеві. Є, звичайно, тільки два логічних значення, правда і неправда, та вони не роблять нові. Значень чисел і рядків значно більше. У стандарті сказано, що існує 18,437,736,874,454,810,627 різних Чисел (у тому числі NaN, число, ім’я якого коротше за “Не число”). Це ніщо в порівнянні з кількістю різних можливих рядків, які, на мою думку, є (2 144,115,188,075,855,872 – 1) ÷ 65,535… хоча я, можливо, помилився.

Набір об’єктивних значень, однак, є відкритим. Кожен об’єкт – унікальна, дорогоцінна сніжинка. Щоразу, коли ви відкриваєте веб-сторінку, створюється поспіх нових об’єктів.

Символи ES6 – це значення, але вони не є рядками. Вони не об’єкти. Вони щось нове: значення сьомого типу.

Поговоримо про сценарій, коли вони можуть стати в нагоді.

Один простий маленький булевий

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

Наприклад, припустимо, що ви пишете бібліотеку JS, яка використовує переходи CSS, щоб зробити елементи DOM на екрані. Ви помітили, що намагатися застосувати одночасно кілька переходів CSS до одного div не працює. Це викликає потворні, переривчасті “стрибки”. Ви думаєте, що можете це виправити, але спочатку вам потрібен спосіб з’ясувати, чи рухається певний елемент.

Як ви можете це вирішити?

Один із способів – використовувати API CSS, щоб запитувати браузер, чи рухається елемент. Але це звучить як надмірність. Ваша бібліотека повинна вже знати, що елемент рухається; це код, який встановлює його переміщення в першу чергу!

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

Хм. Лінійний пошук буде повільним, якщо масив великий.

Що ви дійсно хочете зробити, це просто встановити прапор на елемент:

if  (element.isMoving) {
smoothAnimations (element);
}
element.isMoving = true;

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

  1. Інший код, який використовує for-in або Object.keys() може наткнутися на створене вами майно.
  2. Інший розумний автор бібліотеки, можливо, спочатку подумав про цю методику, і ваша бібліотека погано взаємодіяла б із цією існуючою бібліотекою.
  3. Інший розумний автор бібліотеки може подумати про це в майбутньому, і ваша бібліотека погано взаємодіє з цією майбутньою бібліотекою.
  4. Стандартний комітет може вирішити додати .isMoving() метод до всіх елементів. Тоді ти справді шланг!

Звичайно, ви можете вирішити три останні проблеми, вибравши рядок настільки нудний або настільки нерозумний, що ніхто інший ніколи не назве нічого такого:

If (element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__) {
smoothAnimations(element);
}
element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__ = true;

Це здається не зовсім вагомим напруженням очей.

Ви можете створити практично унікальне ім’я для властивості за допомогою криптографії:

// get 1024 Unicode characters of gibberish
var isMoving = SecureRandom.generateName();

if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;

object[name] синтаксис дозволяє використовувати буквально будь-який рядок в якості імені властивості. Так це спрацює: зіткнення практично неможливо, і ваш код виглядає нормально.

Але це призведе до поганого досвіду налагодження. Кожного разу, коли ви будете console.log() мати елемент із цією властивістю, ви будете шукати величезну кількість сміття. А що, якщо вам потрібно більше однієї подібної властивості? Як ви тримаєте їх правильно? Вони будуть мати різні назви кожного разу, коли ви перезавантажуєтеся.

Чому це так важко? Ми просто хочемо одного маленького булевого!

Символи – відповідь

Символи – це значення, які програми можуть створювати та використовувати як ключі властивостей, не ризикуючи зіткненнями імен.

var mySymbol = Symbol();

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

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

obj[mySymbol] = “ok!”;  // guaranteed not to collide
console.log(obj[mySymbol]);  // ok!

Ось як можна використати символ у ситуації, обговореній вище:

// create a unique symbol
var isMoving = Symbol(“isMoving”);

if (element[isMoving]) {
smoothAnimations(element);
}
element[isMoving] = true;

Кілька приміток щодо цього коду:

  • Рядок “isMoving”в Symbol (“isMoving”) називається описом. Це корисно для налагодження. Він відображається, коли ви пишете символ console.log(), коли ви перетворюєте його в рядок за допомогою .toString() та, можливо, у повідомленнях про помилки. Це все.
  • Element [isMoving] називається властивістю, що керує символом. Це просто властивість, ім’я якої є символом, а не рядком. Крім того, це в будь-якому випадку нормальна властивість.
  • Як і елементи масиву, до властивостей, заповнених символом, не можна отримати доступ, використовуючи крапковий синтаксис, як у obj.name. Доступ до них потрібно використовувати за допомогою квадратних дужок.
  • Доступ до символу, керованого символом, тривіально, якщо ви вже отримали символ. Наведений вище приклад показує, як отримати та встановити element[isMoving], і ми також можемо запитати if (isMoving in element)чи навіть delete element [isMoving] якщо нам потрібно.
  • З іншого боку, все це можливо лише до тих пір, поки isMoving є в обсязі. Це робить символи механізмом слабкої інкапсуляції: модуль, який створює кілька символів для себе, може використовувати їх на будь-яких об’єктах, які він хоче, не боячись зіткнутися з властивостями, створеними іншим кодом.

Оскільки клавіші символів були розроблені для уникнення зіткнень, найпоширеніші функції перевірки об’єктів JavaScript просто ігнорують символьні клавіші. Наприклад, for-in цикл, лише петлі за рядковими клавішами об’єкта. Клавіші символів пропускаються. Object.keys(obj) і Object.getOwnPropertyNames(obj) зробіть те саме. Але символи не зовсім приватні: за допомогою нового API Object.getOwnPropertySymbols(obj) можна перелічити клавіші символів об’єкта. Ще один новий API, Reflect.ownKeys(obj) повертає як рядкові, так і символьні ключі. (Ми будемо повністю обговорювати ReflectAPI в майбутньому дописі).

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

Але які саме символи є?

> typeof Symbol()
“symbol”

Символи точно не схожі на будь-що інше.

Вони незмінно колись створені. Ви не можете встановити на них властивості (і якщо ви спробуєте це в суворому режимі, ви отримаєте TypeError). Вони можуть бути іменами властивостей. Це всі струнні якості.

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

Символи ES6 схожі на більш традиційні символи на таких мовах, як Lisp та Ruby, але не настільки тісно інтегровані в мову. У Lisp всі ідентифікатори є символами. У JS ідентифікатори та більшість ключів властивості досі вважаються рядками. Символи – лише додатковий варіант.

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

> var sym = Symbol(“<3”);
> “your symbol is ” + sym
// TypeError: can’t convert symbol to string
> `your symbol is ${sym}`
// TypeError: can’t convert symbol to string

Ви можете уникнути цього явного перетворення символу в рядку, писати String(sym) або sym.toString().

Три набори символів

Існує три способи отримання символу.

  • Виклик Symbol(). Як ми вже обговорювали, це повертає новий унікальний символ щоразу, коли його називають.
  • Виклик Symbol.for(string). Він отримує доступ до набору існуючих символів, званих реєстром символів. На відміну від унікальних символів, визначених Symbol(), символи в реєстрі символів поділяються. Якщо ви зателефонуєте Symbol.for(“cat”) тридцять разів, він кожен раз повертатиме той самий символ. Реєстр корисний, коли кілька веб-сторінок або декілька модулів у межах однієї веб-сторінки, тоді потрібно спільно використовувати символ.
  • Використовуйте символи типу Symbol.iterator, визначені стандартом. Кілька символів визначені самим стандартом. Кожен має своє особливе призначення.

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

Як специфікація ES6 використовує відомі символи

Ми вже бачили один із способів використання ES6 символом, щоб уникнути конфліктів із існуючим кодом. Кілька тижнів тому в дописі на літераторах ми побачили, що цикл for (var item of myArray) починається з виклику myArray[Symbol.iterator](). Я згадував, що цей метод можна було назвати myArray.iterator(), але символ краще для зворотної сумісності.

Тепер, коли ми знаємо, що таке символи, легко зрозуміти, чому це було зроблено і що це означає.

Ось декілька інших місць, де ES6 використовує відомі символи. (Ці функції ще не реалізовані у Firefox.)

  • Зробити instanceof розширюваним. У ES6, вираз object instanceof constructor задається як метод конструктора: constructor[Symbol.hasInstance](object). Це означає, що він розширюється.
  • Усунення конфліктів між новими функціями та старим кодом. Це серйозно незрозуміло, але ми виявили, що певні Array методи ES6 зламали існуючі веб-сайти лише тим, що вони існували. Інші веб-стандарти мали подібні проблеми: просто додавання нових методів у браузер порушить існуючі сайти. Однак, поломку в основному спричинило те, що називається динамічним визначенням масштабу, тому ES6 запроваджує спеціальний символ Symbol.unscopables, який веб-стандарти можуть використовувати для запобігання певним методам втягнутись у динамічне оцінювання.
  • Підтримка нових видів відповідності рядків. У ES5 str.match(myObject) намагалися перетворити myObject на RegExp. У ES6 він спочатку перевіряє, чи myObject є метод myObject[Symbol.match](str). Тепер бібліотеки можуть надавати власні класи синтаксичного розбору рядків, які працюють у всіх місцях, де RegExp об’єкти працюють.

Кожне з цих застосувань досить вузьке. Важко побачити будь-яку з цих особливостей, яка сама має великий вплив на мій щоденний код. Довгий погляд цікавіший. Добре відомі символи – це вдосконалена версія JavaScript__doubleUnderscores в PHP та Python. Стандарт буде використовувати їх у майбутньому для додавання нових гачків на мову без ризику до наявного коду.

Коли я можу використовувати символи ES6?

Символи реалізовані в Firefox 36 та Chrome 38. Я реалізував їх для Firefox сам, тож якщо ваші символи колись діятимуть як цимбали, ви будете знати, з ким спілкуватися.

Для підтримки браузерів, які ще не мають вбудовану підтримку символів ES6, ви можете використовувати поліфіл, такий як core.js. Оскільки символи не зовсім схожі на що-небудь раніше в мові, поліфіл не є ідеальним. Прочитайте застереження.

Наступного тижня у нас з’являться два нові пости. По-перше, ми розповімо про деякі довгоочікувані функції, які нарешті приходять до JavaScript в ES6 – і обговоримо їх. Почнемо з двох функцій, які відносяться майже до початку програмування. Ми продовжимо дві функції, які дуже схожі, але керуються ефемеронами. Тож, будь ласка, приєднуйтесь до нас наступного тижня, коли ми детально розглянемо колекції ES6.

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

Про Джейсона Орендорфа

Більше статей Джейсона Орендорфа…