# JavaScript

## Що це таке

JavaScript — динамічно типізована мова програмування, стандартизована під назвою ECMAScript, яку
вміє виконувати кожен веббраузер і яка також працює на серверах і машинах збирання через середовища
виконання на кшталт Node.js. Скорочено її називають JS. Щорічні редакції стандарту мають назви
ES2015, ES2024 і так далі, старі номери (ES5, ES6) досі в ужитку, а «vanilla JavaScript» означає
саму мову без фреймворку зверху. Окрім назви та фігурних дужок, із Java у неї мало спільного.

Першу версію написав Brendan Eich у Netscape, і вона вийшла разом із браузером Navigator 2.0. Ecma
International ухвалила перше видання стандарту, ECMA-262, у червні 1997 року, а її комітет TC39
випускає нове видання щороку, починаючи з ECMAScript 2016. Пропозиція потрапляє до специфікації лише
на етапі 4 процесу комітету, а цей етап вимагає реалізацій, які вже випущено, тож текст і рушії
рідко розходяться надовго.

Сама мова майже нічого не каже про зовнішній світ. ECMA-262 визначає синтаксис, типи, об'єкти,
проміси та модулі, а введення й виведення залишає середовищу, що її виконує, — у стандарті воно
зветься хостом. У браузері хост — це вебплатформа: документ, події, таймери й мережеві запити,
описані здебільшого в HTML Standard та споріднених стандартах. У Node.js хост натомість дає файли,
сокети й процеси. Цей поділ пояснює типовий сюрприз: код може бути цілком коректним JavaScript і все
одно зламатися, переїхавши зі сторінки на сервер, бо мова переїхала, а хост лишився на місці.

У браузері скрипти сторінки ділять один головний потік із розкладкою, малюванням і введенням.
Завдання виконується до кінця, перш ніж браузер зможе зреагувати на наступний клік. Проміси та
функції `async` виносять очікування за межі потоку, але обчислення лишаються на ньому, якщо їх не
перенести у Web Worker. Числа — це значення IEEE 754 подвійної точності, тож цілі числа точні лише
до 2^53 − 1, а все більше потребує `BigInt`.

## Чому це важливо

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

Скрипт — це також місце, де сторінка найвразливіша. Усе, що в неї впровадили, виконується з повними
правами сторінки: воно може читати те, що вводить читач, і надсилати це туди, куди дозволено
надсилати самій сторінці. Саме цей ризик стримує Content Security Policy, а сторінка зовсім без
скриптів не дає нападникові з чим працювати. Екосистема пакетів відкриває другий фланг. Типове
фронтенд-збирання тягне за собою сотні транзитивних залежностей, і кожна з них — це код, що
виконується з тими самими правами, що й ваш власний.

Інший бік рахунку теж реальний. Коли одна мова працює по обидва боки системи, браузер і сервер
можуть спільно використовувати правило валідації чи форму даних, а деяким продуктам скриптів треба
багато: офлайн-стан, перетягування, мапи, спільна робота в реальному часі. Тож корисне питання
ставлять для кожної функції окремо: чи має це відбуватися в браузері читача без запиту до сервера?
Якщо ні, нехай HTML зробить сервер або збирання, і віддавати варто саме його.

Пов'язаний вибір — JavaScript чи TypeScript. TypeScript додає перевірку типів і крок збирання, а
компілюється назад у звичайний JavaScript. У кодовій базі, яку щотижня змінюють кілька людей,
перевірка типів зазвичай окупається; для покращення на сотню рядків на статичній сторінці вистачить
JavaScript і суворого лінтера.

## Як це виглядає на практиці

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

Публічний сайт — статичне збирання трьома мовами без бекенду часу виконання, і браузер отримує
один-єдиний власний скрипт.
[Цей скрипт є обмеженим винятком](/uk/portfolio/built-a-multilingual-static-site-in-hugo-126/) для
однієї функції: фільтра портфоліо, усі елементи керування якого приховані, доки скрипт не відпрацює.
Решта сайту, 108 файлів шаблонів, серед них 53 часткові шаблони й 18 шорткодів, робить свою роботу
під час збирання. Сторінка записує й ціну цього вибору: усе інтерактивне треба розв'язати під час
збирання, тому пошуковий індекс оцінили й відклали, а не випустили.

Той самий вміст публікується через Gemini і Gopher, два протоколи, у яких для скрипту просто немає
місця.
[Збирання створює 777 документів Gemini і 777 меню Gopher](/uk/portfolio/mirrored-the-site-to-gemini-and-gopher-137/)
з того самого джерела, а вимірювання підтвердило, що HTML не змінився ні на байт. Два альтернативні
подання додають приблизно дванадцять відсотків до ваги HTML на диску. Аргумент сайту в тому, що
документові не потрібен мегабайт скриптів, аби його прочитали, а формат, який узагалі не здатен
вмістити скрипт, показує це найпростіше.

Найбільше JavaScript тут живе в інструментах. Перевірка якості сайту — це тридцять два скрипти й 10
242 рядки JavaScript, найбільший масив коду в репозиторії, і певний час це був єдиний код, якого не
торкалися ні форматувальник, ні лінтер, ні перевірка типів.
[Підведення його під форматувальник і лінтер](/uk/portfolio/brought-the-quality-gate-code-under-a-linter-143/)
почалося з того, що форматувальник припасували до коду: усталені для репозиторію чотири пробіли
переписали б 2 173 рядки найбільшого перевіряльника, а перевизначення на два пробіли для цих файлів
залишило 38 рядків справжнього розходження. Далі лінтер дав 13 висновків, і всі 13 виправили, не
придушивши жодного. Дефект у перевіряльнику проявляється як перевірка, що проходить, коли мала б
упасти, тож власний код перевірки отримує те саме поводження, яке вона дає всьому іншому.

## Де виникають проблеми

Збої нижче випливають із того, як побудована мова, і для кожного є відомі ліки.

**Сторінка намальована, але на дотики реагує із секундною затримкою.** Довге завдання — гідрація
великого бандла, розбір великої відповіді JSON чи сортування тисяч рядків — займає єдиний потік, що
обробляє введення. Надсилайте менше коду для першої взаємодії: розбивайте бандл за маршрутами й
відкладайте решту, діліть довгу роботу на шматки, що повертають керування циклу подій, а важкі
обчислення переносьте у Web Worker. Вимірюйте на повільному телефоні, а не на ноутбуці, на якому код
написали.

**Ціни та ідентифікатори виходять трохи хибними.** Кожне число тут двійкове, з рухомою комою, тож
десяткові дроби округлюються, а дуже великі цілі зливаються із сусідніми. Типова жертва — 64-бітний
ключ бази даних, надісланий як число в JSON:

```js
0.1 + 0.2 === 0.3; // false
Number.MAX_SAFE_INTEGER; // 9007199254740991
9007199254740993 === 9007199254740992; // true
```

Зберігайте суми в цілих найменших одиницях, як-от копійках чи центах, надсилайте великі
ідентифікатори як рядки, а там, де арифметика мусить бути точною, беріть `BigInt`.

**Порівняння пропускає хибне значення.** Оператор `==` перетворює операнди перед порівнянням, тож
`"" == 0` і `"0" == false` обидва істинні. Використовуйте `===` скрізь і доручіть це правилу лінтера
`eqeqeq`, щоб перетворення відбувалося лише тоді, коли код явно про нього просить.

**Текст від користувача зрештою виконується як код.** Присвоєння неперевіреного тексту в
`innerHTML`, складання розмітки зі шматків рядків чи передавання введення в `eval` перетворює дані
на скрипт. Записуйте текст через `textContent`, надсилайте Content Security Policy, що впускає
скрипти лише за nonce або хешем, а список залежностей тримайте коротким і зафіксованим у lockfile,
читаючи, що приносить кожне оновлення.

**Код, що працює в браузері, падає на сервері.** Мова переїхала, хост лишився: `window` і `document`
існують лише в браузері, а Node.js читає файл як CommonJS або як ES-модуль залежно від налаштувань,
яких браузер ніколи не бачить. Тримайте виклики, специфічні для хоста, на краях, явно задайте
`"type": "module"` у `package.json` і запускайте кожну частину коду в тому середовищі, де вона
справді працюватиме.

## Що почитати далі

- [ECMAScript Language Specification](https://tc39.es/ecma262/) — жива чернетка ECMA-262, текст,
  який реалізує кожен рушій; її оновлюють щоразу, коли пропозиції досягають етапу 4.
- [The TC39 Process](https://tc39.es/process-document/) — етапи, які проходить пропозиція, перш ніж
  стати частиною мови, і що вимагає кожен із них.
- [ECMA-262 в Ecma International](https://ecma-international.org/publications-and-standards/standards/ecma-262/)
  — опубліковані щорічні видання, коли проєктові треба назвати версію, на яку він орієнтується.
- [HTML Standard: event loops](https://html.spec.whatwg.org/multipage/webappapis.html#event-loops) —
  як браузер планує завдання, мікрозавдання й рендеринг довкола скриптів сторінки.
- [HTML Standard: the script element](https://html.spec.whatwg.org/multipage/scripting.html#the-script-element)
  — класичні та модульні скрипти, а також як `async` і `defer` змінюють момент їхнього запуску.

<https://engineer.company/uk/glossary/javascript/>
