Перейти до вмісту

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 своїм читачам, зате чимало його виконує під час збирання. Три сторінки показують обидві половини.

Публічний сайт — статичне збирання трьома мовами без бекенду часу виконання, і браузер отримує один-єдиний власний скрипт. Цей скрипт є обмеженим винятком для однієї функції: фільтра портфоліо, усі елементи керування якого приховані, доки скрипт не відпрацює. Решта сайту, 108 файлів шаблонів, серед них 53 часткові шаблони й 18 шорткодів, робить свою роботу під час збирання. Сторінка записує й ціну цього вибору: усе інтерактивне треба розв’язати під час збирання, тому пошуковий індекс оцінили й відклали, а не випустили.

Той самий вміст публікується через Gemini і Gopher, два протоколи, у яких для скрипту просто немає місця. Збирання створює 777 документів Gemini і 777 меню Gopher з того самого джерела, а вимірювання підтвердило, що HTML не змінився ні на байт. Два альтернативні подання додають приблизно дванадцять відсотків до ваги HTML на диску. Аргумент сайту в тому, що документові не потрібен мегабайт скриптів, аби його прочитали, а формат, який узагалі не здатен вмістити скрипт, показує це найпростіше.

Найбільше JavaScript тут живе в інструментах. Перевірка якості сайту — це тридцять два скрипти й 10 242 рядки JavaScript, найбільший масив коду в репозиторії, і певний час це був єдиний код, якого не торкалися ні форматувальник, ні лінтер, ні перевірка типів. Підведення його під форматувальник і лінтер почалося з того, що форматувальник припасували до коду: усталені для репозиторію чотири пробіли переписали б 2 173 рядки найбільшого перевіряльника, а перевизначення на два пробіли для цих файлів залишило 38 рядків справжнього розходження. Далі лінтер дав 13 висновків, і всі 13 виправили, не придушивши жодного. Дефект у перевіряльнику проявляється як перевірка, що проходить, коли мала б упасти, тож власний код перевірки отримує те саме поводження, яке вона дає всьому іншому.

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

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

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

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

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 — жива чернетка ECMA-262, текст, який реалізує кожен рушій; її оновлюють щоразу, коли пропозиції досягають етапу 4.
  • The TC39 Process — етапи, які проходить пропозиція, перш ніж стати частиною мови, і що вимагає кожен із них.
  • ECMA-262 в Ecma International — опубліковані щорічні видання, коли проєктові треба назвати версію, на яку він орієнтується.
  • HTML Standard: event loops — як браузер планує завдання, мікрозавдання й рендеринг довкола скриптів сторінки.
  • HTML Standard: the script element — класичні та модульні скрипти, а також як async і defer змінюють момент їхнього запуску.

Словник