JavaScript
JavaScript — мова програмування, яку виконує кожен браузер: один потік на сторінку, один стек викликів і черга для всього, що мусить чекати. Більшість того, що здається в ній довільним, випливає з цієї моделі або з рішення перших років мови, яке веб уже не може скасувати. Сторінка починається з дванадцяти несподіванок — кожна з правилом, що за нею стоїть, і виправленням; далі — цикл подій, де асинхронний код губить час і помилки, числа й посилання, область видимості, модулі й дерево залежностей і п’ять задокументованих кейсів.
- Потоків
- Один на сторінку, плюс воркери
- Стандарт
- ECMAScript 2026 · щороку
- Найбільше точне ціле
- 2^53 − 1
Дванадцять несподіванок і правило за кожною
Спершу таблиця, бо саме через це найчастіше відкривають сторінку про JavaScript: мова щойно зробила щось, схоже на баг. Кожен рядок — це те, що ви бачите, правило, яке це спричиняє, і виправлення. Жоден із дванадцяти не є багом. Кожен — це правило, записане в специфікації, яке зустріло код людини, що з ним ще не стикалася, — а правил так мало, що решта сторінки охоплює кожне з них.
| Що ви бачите | Що насправді відбувається | Виправлення |
|---|---|---|
1. Колбек промісу виконується раніше за setTimeout(fn, 0), поставлений до нього | Колбеки промісів ідуть у чергу мікрозадач, а її вичерпують повністю, перш ніж узагалі глянути на наступний таймер. | Ніколи не впорядковуйте роботу таймером. Чекайте через await саме те, від чого залежите. |
| 2. Сторінка замерзає, поки йде обчислення | Потік один. Поки ваша функція в стеку, не виконується нічого іншого — ні обробник кліку, ні відмальовка. | Поділіть роботу на шматки й віддавайте керування між ними — або винесіть її у Web Worker. |
3. Три await поспіль тривають стільки, скільки всі три разом | Кожен await завершується раніше, ніж наступний запит узагалі стартує. | Запускайте незалежні запити разом і чекайте Promise.all. |
4. forEach з async-колбеком не чекає | forEach ігнорує те, що повертає колбек, — тут це проміс — і одразу йде далі. | for…of з await усередині або await Promise.all(items.map(…)). |
5. try / catch не ловить помилку | Помилку кинуто пізніше, на іншому оберті циклу, коли блок try уже завершився. | Робіть await усередині try або чіпляйте .catch() там, де віддаєте проміс. |
6. 0.1 + 0.2 дорівнює 0.30000000000000004 | Кожне число — це 64-бітне двійкове число з рухомою комою, а 0,1 не має точного двійкового запису. | Рахуйте гроші в цілих дрібних одиницях (центах, копійках); округлюйте лише для показу. |
| 7. ID із сервера повертається з іншими останніми цифрами | Цілі понад 2^53 − 1 неможливо зберегти точно, і JSON.parse мовчки їх округлює. | Передавайте великі ID рядками. Для арифметики над ними — BigInt. |
| 8. Зміна копії змінює оригінал | Розкладання й Object.assign копіюють один рівень. Вкладені об’єкти лишаються спільними. | structuredClone() для глибокої копії — або копіюйте кожен рівень, який змінюєте. |
9. Чинний 0 чи '' підміняється значенням за замовчуванням | || вважає відсутнім кожне хибне значення — зокрема 0, порожній рядок і false. | ?? для значень за замовчуванням і ?. для необов’язкового доступу: обидва означають «null або undefined» — і нічого більше. |
| 10. Колбеки, створені в циклі, бачать лише останнє значення | З var уся функція має одне зв’язування, і кожен колбек замикається саме на ньому. | let або const: кожна ітерація циклу отримує власне зв’язування. |
11. [10, 9, 1].sort() повертає [1, 10, 9] | Без функції порівняння sort перетворює значення на рядки й порівнює їх символ за символом. | (a, b) => a - b для чисел, Intl.Collator для слів, toSorted() — щоб не чіпати оригінал. |
| 12. Дата показується на день раніше | new Date('2026-09-30') — це північ за UTC, а в Нью-Йорку тоді ще 29 вересня. | Зберігайте календарну дату як дату, а не як момент часу: Temporal.PlainDate або сам рядок. |
Один потік, дві черги
JavaScript у вкладці браузера виконується в одному потоці. Стек викликів один — це список функцій, що виконуються зараз, і кожна чекає, доки поверне та, що над нею, — і поки в ньому хоч щось є, у цій вкладці не виконується більше нічого. Ні інший обробник, ні колбек таймера, ні відмальовка. Саме останнє й означає «заблокувати інтерфейс» буквально: браузер не може намалювати кадр, бо ваш код іще не повернувся. Це рядок 2 таблиці.
Коли стек порожніє, керування бере цикл подій, і він поводиться з двома чергами по-різному. Він повністю вичерпує чергу мікрозадач — кожен колбек промісу й усі нові мікрозадачі, які ці колбеки додають під час вичерпування. Потім бере рівно одну макрозадачу: один колбек таймера, один обробник кліку, одну мережеву відповідь. Потім браузер може намалювати кадр — і оберт починається знову. Ця асиметрія — увесь рядок 1. Колбек промісу не був швидшим за таймер; він стояв у черзі, яку завжди вичерпують першою.
| Де код чекає | Що туди потрапляє | Коли виконується | Пастка |
|---|---|---|---|
| Стек викликів | Звичайний синхронний код | Зараз і до кінця | Нічого не малюється, доки він не поверне (рядок 2) |
| Черга мікрозадач | Колбеки промісів, код після кожного await, queueMicrotask | Усі, щойно стек порожніє | Ланцюг, що весь час додає нові, морить сторінку назавжди |
| Черга задач (макрозадачі) | Таймери, події користувача, мережеві відповіді, postMessage | Одна за оберт циклу | Вкладені таймери обмежено щонайменше 4 мс, тож 0 — це прохання, а не обіцянка |
| Кадр анімації | Колбеки requestAnimationFrame | Безпосередньо перед наступною відмальовкою | Важка робота тут затримує саме той кадр, який мала намалювати |
| Web Worker | Окремий скрипт із власним циклом | Паралельно, в іншому потоці | Без DOM, а дані переходять лише скопійованими повідомленнями |
Рядок 2 має своє число. Браузер вважає довгою задачею будь-яку, що тримає потік понад 50 мілісекунд, а з березня 2024 року Google вимірює чуйність як INP — Interaction to Next Paint, один із трьох Core Web Vitalsметрики досвіду сторінки від GoogleТри вимірювання, які Google бере з браузерів реальних відвідувачів: LCP — як швидко з’являється основний вміст; INP — як швидко сторінка відповідає на дії; CLS — наскільки стрибає розкладка. Вони впливають на ранжування в пошуку, але лише як один сигнал із багатьох: швидка сторінка, яку нема сенсу читати, однаково програє повільній, що відповідає на питання.Усі терміни, — де 200 мілісекунд від кліку до наступного кадру вважаються добрим результатом. Цикл, що 400 мілісекунд сортує список, провалює це сам-один, хай як його написано — з async чи без. await нічого не переносить в інший потік: він призупиняє вашу функцію й дає циклу працювати далі, але коли очікуване значення надходить, код після нього виконується в тому самому єдиному потоці.
Де асинхронний код губить час і помилки
Асинхронний код мав три покоління синтаксису, і всі три досі трапляються в реальних проєктах — часто в одному файлі. Під ними той самий механізм: проміс — це об’єкт, що заміняє результат, який ще не готовий, а async і await — спосіб написати код на промісах так, щоб він читався згори донизу. Відрізняється між трьома те, як подорожують помилки і наскільки читабельний порядок подій.
| Стиль | Обробка помилок | Де досі доречний |
|---|---|---|
| Колбеки | За домовленістю — помилка першим аргументом, і це ніщо не забезпечує. | Обробники подій і старі API Node; ніколи для нової послідовної роботи. |
| Проміси | .catch() покриває весь ланцюг вище — один обробник на багато кроків. | Поєднання роботи, що йде одночасно: Promise.all та його родичі. |
| async / await | Звичайний try / catch і стектрейси, у яких названо вашу функцію. | Типовий вибір для всього послідовного — читається в порядку подій. |
Чистий синтаксис робить одну помилку легкою, і це рядок 3. Три незалежні запити, очікувані один за одним, тривають стільки, скільки всі три разом, бо другий не стартує, доки не завершиться перший. Запущені разом і очікувані через Promise.all, вони тривають стільки, скільки найповільніший. У коді ніщо не виглядає хибним: кожен рядок правильний, а сторінка все одно більш ніж удвічі повільніша, ніж могла б бути.
Третій рядок діаграми — це рядок 4 таблиці, і він гірший, ніж просто повільний. forEach тричі викликає ваш async-колбек, отримує три проміси, викидає їх і одразу повертається. Рядок після циклу виконується раніше, ніж надійшов бодай один результат, — а якщо один із трьох запитів упаде, жоден код ніде не чекає, щоб про це дізнатися. for…of з await усередині дає свідомо послідовну версію, await Promise.all(items.map(…)) — версію «все разом».
| Комбінатор | Завершується, коли | Для чого |
|---|---|---|
| Promise.all | Усі успішні — або відхиляється на першій невдачі | Кілька речей, потрібних наступному кроку, кожна з них |
| Promise.allSettled | Усі завершилися, успішно чи ні | Дашборд із віджетів, де падіння одного не має гасити решту |
| Promise.race | Перший завершився, хай як | Тайм-аут: запит проти таймера |
| Promise.any | Перший успішний | Той самий файл із кількох дзеркал — хто відповість |
Дві звички закривають більшу частину цього розділу. Перша: проміс, який ви створили, — це проміс, який ви чекаєте, повертаєте або ловите; правило лінтера, що позначає «завислі» проміси (у typescript-eslint воно зветься no-floating-promises), за хвилину знаходить кожне місце, де ви забули. Друга: усе, що користувач може покинути, має бути скасовуваним. Передавайте у fetch сигнал AbortController і скасовуйте його, коли компонент зникає або змінюється рядок пошуку. Сторінка про React показує, що буває без цього: старіша, повільніша відповідь приходить другою й затирає новішу.
Числа, посилання й відсутні значення
У JavaScript майже для всього є один вид чисел, і це 64-бітне двійкове число з рухомою комою — double за стандартом IEEE 754. Воно зберігає приблизно 15–17 значущих десяткових цифр і зберігає дроби у двійковій системі. Це й увесь рядок 6: 0,1 у двійковому записі — нескінченний дріб, як одна третя в десятковому, тож зберігається округленим, а сума двох округлених значень це округлення показує. Кожна мова, що використовує double, дає ту саму відповідь; просто в JavaScript більшість людей зустрічає це вперше.
Рядок 7 — той самий факт на іншому кінці шкали. Double зберігає цілі числа точно лише до 2^53 − 1, тобто до 9 007 199 254 740 991; це значення доступне як Number.MAX_SAFE_INTEGER. Далі проміжок між сусідніми числами, які можна записати, стає 2, потім 4, і непарні числа починають зникати: JSON.parse('{"id": 9007199254740993}').id повертає 9007199254740992 — без помилки й без попередження. Бази даних видають 64-бітні ID як щось звичайне, тож ця межа не теоретична. Twitter наштовхнувся на неї у 2010 році, і розділ із кейсами нижче розповідає як.
Що насправді лежить у змінній
Примітиви — числа, рядки, булеві значення, null, undefined, символи й bigint — копіюються при присвоєнні. Усе інше є об’єктом, і змінна не містить об’єкта: вона містить посилання на нього, адресу. Тож дві змінні можуть називати один і той самий об’єкт, і зміна через одну видима через іншу. Масиви й функції теж об’єкти, і тому сортування масиву всередині функції змінює масив того, хто її викликав.
Діаграма — це рядок 8. Розкладання об’єкта — { ...original } — створює новий об’єкт і копіює його верхній рівень, а однією з речей на верхньому рівні є посилання на вкладену адресу. Тож копія й оригінал тепер ділять її, і зміна copy.user.city з’являється в original.user.city. structuredClone(), вбудований в усі основні браузери з 2022 року, копіює кожен рівень і вміє працювати з датами, мапами, множинами та циклічними посиланнями. Він замінив старий трюк JSON.parse(JSON.stringify(x)), який мовчки викидав функції та undefined і перетворював кожну дату на рядок.
Посилання — це ще й причина, чому фреймворки порівнюють стан за тотожністю. React вирішує, чи змінився стан, питаючи, чи нове значення — те саме посилання, що й старе, тож push у масив на місці дає йому ту саму адресу, і екран не оновлюється. Виправлення має ту саму форму, що й рядок 8, лише навиворіт: створюйте новий об’єкт для кожного рівня, який змінюєте, а решту свідомо лишайте спільною.
Відсутнє, порожнє й хибне
У мові є два значення для «нічого» — undefined, яке дає незадана змінна чи відсутня властивість, і null, яке хтось записав навмисно, — і короткий список значень, що в умові вважаються хибними: false, 0, порожній рядок, NaN, null і undefined, а ще рідкісні -0 та 0n. Рядок 9 живе в проміжку між цими двома списками. Нульова кількість і порожнє ім’я — справжні відповіді, а || їх викидає.
| Ви пишете | Вважає відсутнім | Годиться для |
|---|---|---|
| x || fallback | Кожне хибне значення, зокрема 0 і '' | Майже нічого — це рядок 9 |
| x ?? fallback | Лише null і undefined | Значення за замовчуванням для налаштувань і параметрів |
| x?.y | Лише null і undefined — і зупиняється на них | Читання даних, яких може не бути |
| x == null | Лише null і undefined | Єдине місце, де нестрога рівність ідіоматична |
Щодо рівності правило коротке: використовуйте === і не думайте про це. Нестрога рівність застосовує таблицю перетворень, чиї результати можна обґрунтувати поодинці й неможливо передбачити разом, і жодна кодова база ніколи не стала кращою, спираючись на неї. Два винятки варто знати. NaN === NaN — хибне, бо «не число» не дорівнює нічому, включно із собою, тож перевіряйте через Number.isNaN(). А typeof null дорівнює 'object' — баг 1995 року, який так і лишився: пізнішу пропозицію його виправити відхилили, бо на нього вже спиралося забагато коду.
Область видимості, замикання і вбудоване, що не зміниться
Замикання — це функція, яка пам’ятає змінні місця, де її створили, і тримає їх живими стільки, скільки живе сама. Це механізм, завдяки якому колбеки працюють пізніше, існує приватний стан у модулі й працює кожен хук у React. І це рядок 10. З var змінна належить усій функції, тож десять створених у циклі колбеків замикаються на одній і тій самій змінній і всі бачать її кінцеве значення. З let кожна ітерація циклу отримує свіжу змінну, і той самий код робить те, чого від нього всі чекали.
Це й є практична причина, чому var зник із нового коду. Його область — функція, а не блок, і він піднімається, тож витікає з блоків if і for та існує — як undefined — ще до рядка, що його оголошує. let і const мають блокову область, а читання їх до оголошення — це помилка, а не мовчазне undefined. Типовим вибором має бути const: він забороняє перепризначати ім’я, а це саме та помилка, яку варто попереджати, — і водночас дозволяє змінювати об’єкт, на який ім’я вказує; знову рядок 8.
this визначається тим, як функцію викликали, а не тим, де її написали, тож передача obj.method як колбека губить obj, і this стає undefined. Стрілкові функції не мають власного this і беруть його з коду навколо, тому вони й стали типовим вибором для колбеків. Якщо ви пишете .bind(this) чи const self = this, ви обходите звичайну функцію, яка мала бути стрілковою.
Дві вбудовані функції, застиглі в першому році
Рядки 11 і 12 зовсім не про модель виконання. Це поведінка, яку мова отримала рано й не може змінити, бо перше правило вебу — оновлення браузера не має ламати робочий сайт. sort() без аргументу порівнює значення як рядки, тож 10 стає перед 9 рівно так, як «10» стоїть перед «9» у словнику. Date розбирає голу дату на кшталт '2026-09-30' як північ за UTCCoordinated Universal Time — світовий еталонний часСтандарт часу, у якому зазвичай ведуть годинники й логи, — без часових поясів і переходу на літній час. Він додає високосні секунди, щоб не відставати від обертання Землі, тому час GPS, який цього не робить, нині випереджає його на 18 секунд.Усі терміни, а дату з часом і без зсуву — як місцевий час, і потім друкує обидві в місцевому часовому поясі. Тож дата, обрана на ноутбуці в Києві, там показується правильно, а в колеги в Нью-Йорку — на день раніше.
Для обох є сучасні заміни, які стоять поряд зі старою поведінкою, а не змінюють її. toSorted() (2023) повертає відсортовану копію, а не переставляє масив на місці. Temporal, який став частиною стандарту мови у 2026 році й працює в Chrome і Firefox, — Safari ще наздоганяє, тож там допомагає поліфіл, — розводить те, що Date змішує: Temporal.PlainDate — це календарна дата без часового поясу, Temporal.Instant — мить на часовій осі, а Temporal.ZonedDateTime — мить у конкретному місці. День народження — це PlainDate. Час платежу — Instant.
Модулі, збірка і дерево залежностей
Модуль — це файл із власною областю видимості, який явно каже, що він бере з інших файлів і що пропонує їм. У JavaScript щодня використовуються дві системи модулів, і проєкт, що їх змішує, ловить помилки, які не мають сенсу, доки не знаєш, що систем дві.
| ES-модулі | CommonJS | |
|---|---|---|
| Синтаксис | import / export | require() / module.exports |
| Залежності відомі | До виконання будь-якого коду — імпорти статичні | Лише під час виконання — require це виклик функції |
| Невикористані експорти | Можна довести, що не використовуються, і викинути з бандла | Зазвичай потрапляють у бандл усе одно |
| Де вона за замовчуванням | Браузери й кожен новий пакет | Старіший код і пакети Node.js |
Між кодом, який ви пишете, і кодом, який виконує браузер, зазвичай стоїть збірка: складальник, що зводить сотні модулів у кілька файлів, компілятор, що перетворює TypeScript і новіший синтаксис на те, що розуміють цільові браузери, і мініфікатор, що скорочує кожне ім’я. З цього два наслідки. Помилка в продакшені вказує на рядок 1, стовпчик 48 213 файлу, якого ви ніколи не писали, тож збірка без source maps — файлів, що відображають результат назад на ваш код, — викинула ваші стектрейси. А TypeScript зникає до того, як щось виконується: він перевіряє форми, які ви оголосили, і не може перевірити відповідь із мережі, поки ви не валідуєте її самі.
А ще є те, чого ви не писали взагалі. npm install із трьома пакетами в package.json звично приносить сотні, бо кожен із ваших трьох має власні залежності, а ті — свої. Медіанна вебсторінка сьогодні надсилає на телефон 558 КБ JavaScript, і 44% цього не використовуються під час завантаження сторінки (Web Almanac, 2024). Кожен із цих пакетів супроводжує людина, яку ви ніколи не бачили і яка будь-якого дня може передати його комусь або прибрати.
Діаграма показує, чому це структурний ризик, а не питання вибору добрих пакетів. Пакет, що створює проблему, рідко той, який ви обрали. Це листок на три рівні нижче, крихітний, з одним супровідником, до якого ведуть одразу кілька ваших залежностей, — рівно така форма була в left-pad у 2016 році та в event-stream у 2018-му, обидва є серед кейсів нижче. Заходи, що працюють, стосуються всього цього дерева, а не трьох ваших прямих виборів:
- lockfileточні версії кожної залежностіФайл, який менеджер пакетів пише поряд зі списком залежностей і в якому записано точну версію кожного встановленого пакета — зокрема тих, що підтягнули ваші залежності. Закомічений у репозиторій, він робить кожне встановлення однаковим; без нього дві збірки того самого коміту можуть виконувати різний код.Усі терміни у репозиторії —
package-lock.jsonчи його відповідник, — що фіксує точну версію кожного вузла дерева, а не лише трьох, які ви назвали. npm ciзамістьnpm installу пайплайні, щоб збірка встановлювала рівно те, що каже lockfile, або падала.- Оновлення, що приходять як переглянуті pull requestзміна, запропонована на рев’юЗапит на злиття однієї гілки в іншу, де зміну переглядають, обговорюють і перевіряють пайплайном, перш ніж вона потрапить у головну гілку; у GitLab те саме зветься merge request. Його ціна — очікування: pull request, що днями чекає на рев’ю, і породжує довгоживучі гілки та болісні злиття.Докладніше в статті →Усі терміни, із затримкою в кілька днів після релізу, — захоплені версії часто знаходять і знімають за години чи дні, і затримка тримає вас поза цим вікном.
- Залежність додається лише після питання, скільки коштувало б написати це самому; функцію з одинадцяти рядків дешевше мати свою, ніж залежати від чужої.
- Сторонні скрипти, які віддаються з вашого власного домену або прибиті хешем Subresource Integrity, а не підвантажуються наживо з домену, яким керує хтось інший.
П’ять кейсів і те, чого в кожному бракувало
Кожен кейс нижче задокументовано публічно — компанією, реєстром пакетів або дослідниками, що ним займалися. У жодному не бракувало чогось хитромудрого. Бракувало рядка замість числа, зафіксованої версії, рев’ю або скрипта, що віддається з власного сервера.
| Кейс | Що сталося | Чого бракувало |
|---|---|---|
| ID у Twitter, 2010 | Нові ID твітів от-от мали перевищити 53 біти, яких JavaScript точно не втримує | ID, передані рядками, — додані як id_str (рядок 7) |
| left-pad, 2016 | Пакет з одинадцяти рядків зняли з публікації, і збірки Babel та Atom падали близько двох з половиною годин | Копія того, від чого ви залежите, і реєстр, який не може видалити її з-під вас |
| SmooshGate, 2018 | Новий стандартний метод зламав сайти на старій бібліотеці, тож стандарт перейменував свій метод | Нічого з боку сайтів — і саме тому мова зберігає свої старі помилки |
| event-stream, 2018 | Новий супровідник додав залежність, яка крала в користувачів одного біткоїн-гаманця | Переглянуті оновлення дерева, а не лише власного коду |
| polyfill.io, 2024 | Домен скрипта, що стояв на 100 000+ сайтів, продали, і скрипт почав перенаправляти користувачів телефонів | Скрипт, що віддається з вашого власного сервера |
left-pad — це дерево залежностей у найменшому масштабі. 22 березня 2016 року після суперечки через назву з компанією Kik розробник зняв з npm усі свої 273 пакети. Один із них, left-pad, складався з одинадцяти рядків, що доповнювали рядок до потрібної довжини. Він стояв під line-numbers, який просив рівно версію 0.0.3, а line-numbers стояв під Babel та Atom, — тож збірки по всій екосистемі почали падати за лічені хвилини. Хтось перевидав left-pad за десять хвилин, але як версію 1.0.0, а вона не задовольняла «рівно 0.0.3». npm відновив оригінал о 16:55 за тихоокеанським часом, приблизно через дві з половиною години, а потім змінив правила, щоб пакет, від якого залежать інші, більше не можна було просто зняти.
Останні два кейси — та сама атака на двох різних рівнях. event-stream була бібліотекою потоків із приблизно 1,5 млн завантажень на тиждень, і від неї залежало близько 1 600 пакетів. У 2018 році її автор, який сам нею вже не користувався, прийняв пропозицію допомоги й дав права на публікацію незнайомцю. Незнайомець додав залежність flatmap-stream, чий вміст спав, доки не опинявся у збірці біткоїн-гаманця Copay, а там намагався вкрасти ключі гаманців із великими балансами. Це знайшли в листопаді, після мільйонів встановлень. polyfill.io був тегом script на понад 100 000 сайтів, що підвантажувався наживо з чужого домену. У лютому 2024 року домен і GitHub-акаунт проєкту продали, і до червня скрипт перенаправляв користувачів телефонів на сайти ставок. Його початковий автор уже встиг сказати всім прибрати його, бо сучасним браузерам він більше не потрібен.
Де це вироджується
JavaScript рідко ламається так, щоб зупинити сторінку. Він ламається тим, що працює повільніше, ніж мав би, губить помилку там, куди ніхто не дивиться, або постачає код незнайомця. Ось форми, яких це набуває.
- Незалежні виклики, очікувані поспіль, перетворюють 400-мілісекундну сторінку на 900-мілісекундну без видимої причини.
async-колбек, переданий уforEach, повертається одразу й нічого не чекає.- Проміси, створені й ніколи не очікувані та не спіймані, у Node.js завершують процес.
||для значень за замовчуванням там, де мався на увазі??, — і чинний0чи порожній рядок підміняється.- Гроші в числах із рухомою комою, де
toFixedховає похибку, доки не почнеться звірка підсумків. - Поверхневі копії, сприйняті як глибокі, — вкладена зміна досягає оригіналу.
- Довга синхронна робота в головному потоці, яка морозить сторінку, хоч би як її написали.
- Слухачі, які додають на кожному екрані й ніколи не прибирають, — вкладка росте, доки її не закриють.
- Залежність, додана, щоб заощадити десять рядків, і живий тег script із домену, яким керує хтось інший.
- TypeScript, сприйнятий як валідація, — дані з мережі вважають надійними, бо їхній тип оголошено.
Є й чесна межа, і вона не про мову. Мова скінченна. Усе з таблиці нагорі можна вивчити за тиждень, а більшість решти зловить лінтер. Чого вивчити неможливо — це дерево залежностей із кількох сотень пакетів, написаних людьми, яких ви ніколи не зустрінете, і жоден інструмент не прочитає його за вас: аудит розповідає про вразливості, про які вже хтось повідомив, а не про супровідника, який наступного місяця передасть пакет незнайомцю. Заходи з розділу про модулі зменшують ризик. Зменшити саму відкритість до нього можуть лише менше залежностей.
Друга половина межі — пристрій. Ніщо в мові не скаже, як ваш код відчувається на Android-телефоні середнього класу зі слабким з’єднанням, а саме там значна частка ваших користувачів. Сторінка, що відповідає за 80 мс на ноутбуці розробника, на телефоні може витрачати в кілька разів більше. Відкрийте панель Performance з увімкненим уповільненням CPUCentral Processing Unit — процесорПроцесор, що виконує інструкції програми. Коли кажуть, що завдання «впирається в CPU», мають на увазі, що воно обмежене обчисленнями, а не очікуванням диска чи мережі, — і лікуються ці два випадки протилежно, тому розрізняти їх варто одразу.Усі терміни, а ще краще — візьміть справжній телефон і пройдіть ту одну задачу, заради якої сторінка існує. Це єдина перевірка на цій сторінці, що вимірює те, що відчуває користувач, а не те, що каже код.
Коли це застосовувати
Застосовуйте, коли
- Усе, що виконується в браузері: альтернатив немає, є лише мови, що компілюються в неї.
- Додавайте TypeScript, щойно в проєкті більше одного учасника або він переживає пам’ять однієї людини.
- Беріть Web Worker, коли робота справді впирається в CPU й не має блокувати інтерфейс.
- Беріть
Promise.allщоразу, коли очікувані виклики не залежать один від одного.
Уникайте, коли
- Покладатися на нестрогу рівність чи «правдивість» там, де
0або порожнє значення є чинними. - Числа з рухомою комою для грошей — рахуйте цілі дрібні одиниці.
- Важкі обчислення в головному потоці, коли користувач має продовжувати взаємодію.
- Вважати, що TypeScript валідує дані під час виконання, — ні; розбирайте те, що перетинає межу.
Джерела й метод
На чому стоїть ця стаття
- 12 джерел
- 3 задокументовані збої
- Перевірено 13 жовтня 2026 р.
Практики й походження термінів
правила за дванадцятьма сюрпризами, цикл подій і таймери- СтандартECMAScript Language Specification (ECMA-262) (відкривається в новій вкладці)Ecma TC39
- СтандартHTML Standard — Web application APIs: event loops and timers (відкривається в новій вкладці)WHATWG
- ДокументаціяMDN — JavaScript (відкривається в новій вкладці)MDN Web Docs
- СтаттяInteraction to Next Paint becomes a Core Web Vital on March 12 (відкривається в новій вкладці)web.dev · 2024
- ДослідженняThe Web Almanac 2024 — JavaScript (відкривається в новій вкладці)HTTP Archive · 2024
Збої, на яких вчиться стаття
left-pad, event-stream і polyfill.io у «П’ять кейсів»- Постмортемkik, left-pad, and npm (відкривається в новій вкладці)npm Blog · 23.03.2016
- ПостмортемDetails about the event-stream incident (відкривається в новій вкладці)npm Blog · 26.11.2018
- ПостмортемA post-mortem of the malicious event-stream backdoor (відкривається в новій вкладці)Snyk · 2018
- СтаттяPolyfill supply chain attack hits 100K+ sites (відкривається в новій вкладці)Sansec · 25.06.2024
Два інші кейси
64-бітні ID Twitter і SmooshGateДослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.
Було корисно?
Поділіться з тим, хто працює над тією ж задачею.