Senior: питання на співбесіді з теми «Браузерні API й мережа»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
Web Worker - JavaScript, що виконується в окремому потоці. Важкі обчислення у воркері не блокують головний потік, тож інтерфейс лишається чутливим до кліків і прокрутки.
// main.js - синтаксис, який розуміє Vite
const worker = new Worker(new URL('./csv-worker.js', import.meta.url), { type: 'module' });
worker.postMessage({ file });
worker.onmessage = (event) => renderTable(event.data.rows);
worker.onerror = (event) => showError(event.message);
// csv-worker.js
self.onmessage = async (event) => {
const text = await event.data.file.text();
const rows = parseCsv(text); // секунди роботи - але не в головному потоці
self.postMessage({ rows });
};
Обмеження воркерів:
- немає доступу до DOM,
window,document. Лише обчислення й мережа (fetch, WebSocket),IndexedDB, таймери; - спілкування лише повідомленнями. Дані копіюються (алгоритм structured clone): передати мегабайтний масив туди й назад - теж робота. Функції й класи з методами не передаються;
- запуск коштує - завантаження скрипта й ініціалізація. Для обчислень на кілька мілісекунд воркер повільніший за звичайний виклик.
Передача без копіювання - transferable objects:
const buffer = new ArrayBuffer(50_000_000);
worker.postMessage(buffer, [buffer]); // власність переходить до воркера
buffer.byteLength; // 0 - у головному потоці буфер більше недоступний
Працює для ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas, потоків.
Коли воркер доречний:
- розбір великих файлів (CSV, Excel, JSON на десятки мегабайтів);
- обробка зображень, стиснення перед завантаженням на сервер;
- криптографія, хешування файлів;
- пошук і фільтрація по великому локальному набору даних;
- рендер у
OffscreenCanvas(графіки, візуалізації).
Коли не допоможе:
- повільний рендер великого списку - проблема в DOM, а DOM недоступний воркеру. Тут допомагає віртуалізація;
- очікування мережі - воно й так не блокує потік.
Різновиди:
- dedicated worker - належить одній сторінці (приклад вище);
- SharedWorker - один на кілька вкладок того самого сайту: одне спільне WebSocket-з'єднання на всі вкладки;
- Service Worker - зовсім інша роль: проксі між сторінкою й мережею.
Зручність: бібліотека Comlink перетворює обмін повідомленнями на виклик звичайних async-функцій, приховуючи postMessage.
Service Worker - скрипт, який браузер запускає у фоні між сторінкою й мережею. Він перехоплює запити сторінок свого сайту й вирішує, що відповісти: з мережі, з кешу чи згенерувати відповідь сам. На ньому будуються офлайн-режим, PWA, push-сповіщення, фонова синхронізація.
// реєстрація на сторінці
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js
const CACHE = 'static-v3';
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(['/offline.html'])));
});
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(fetch(event.request).catch(() => caches.match('/offline.html')));
}
});
Життєвий цикл:
- install - підготовка: завантажити й закешувати потрібні файли;
- waiting - новий воркер чекає, доки закриються всі вкладки зі старим. Це захищає від ситуації, коли одна сторінка працює зі старими файлами, а запити обслуговує новий воркер;
- activate - новий воркер бере керування; тут видаляють старі кеші;
- fetch - перехоплення запитів.
self.skipWaiting() і clients.claim() прискорюють перемикання, але ціною можливої невідповідності версій.
Стратегії кешування:
- cache first - для хешованих статичних файлів, що не змінюються;
- network first - для HTML і API: свіже, а кеш лише як запас при відсутності мережі;
- stale-while-revalidate - миттєво з кешу, паралельно оновити кеш з мережі.
Бібліотека Workbox реалізує ці стратегії й генерує воркер під час збирання (у Vite - vite-plugin-pwa).
Чим небезпечна помилка:
- воркер живе довше за сторінку. Він встановлений у браузері користувача й перехоплює запити навіть після того, як ви виправили сайт. Якщо воркер помилково кешує HTML «назавжди», користувачі бачитимуть стару версію сайту, і деплой цього не виправить;
- оновлення самого воркера - браузер перевіряє, чи змінився
sw.js, при навігації на сайт (за замовчуванням в обхід HTTP-кешу, і щонайменше раз на 24 години). URLsw.jsмає лишатися незмінним: якщо перейменувати файл, старий воркер про новий так і не дізнається; - план відступу: заздалегідь мати «вимикач» - новий
sw.js, що видаляє кеші й знімає реєстрацію (self.registration.unregister()).
Обмеження: лише HTTPS (виняток - localhost), без доступу до DOM, область дії (scope) - каталог, з якого віддано скрипт, і нижче.
Для звичайного сайту (не PWA) Service Worker часто зайвий: HTTP-кешування з хешованими іменами файлів дає більшу частину вигоди без ризиків.
Дві суперечливі вимоги: статичні файли мають кешуватися якомога довше (швидкість, трафік), а після деплою користувачі мають одразу отримати нову версію. Розв'язок - різна політика для HTML і для ресурсів з хешем у назві.
1. Ресурси з хешем у назві (app-3f9a1c.js, app-8b2e.css - так їх називає Vite) - кешувати «назавжди»:
Cache-Control: public, max-age=31536000, immutable
Вміст файлу ніколи не змінюється: нова версія - нова назва. immutable каже браузеру не перевіряти файл навіть при оновленні сторінки.
2. HTML - завжди перевіряти актуальність:
Cache-Control: no-cache
no-cache не означає «не кешувати» - браузер зберігає копію, але перед використанням питає сервер, чи вона актуальна. Не кешувати взагалі - це no-store (для сторінок з персональними даними).
Саме HTML посилається на конкретні хешовані файли: новий HTML після деплою - нові назви ресурсів - браузер їх завантажить.
Перевірка актуальності (ревалідація):
- сервер віддає
ETag: "abc123"чиLast-Modified; - браузер наступного разу питає
If-None-Match: "abc123"; - якщо не змінилося -
304 Not Modifiedбез тіла: економія трафіку, але все одно запит по мережі.
Типові помилки:
- довге кешування файлів без хешу (
/js/app.jsзmax-age=86400) - після деплою користувачі добу бачать старий JavaScript з новим HTML, і все ламається; - старі файли видалено при деплої. Користувач з відкритою вкладкою (або HTML з кешу CDN) просить
app-старийхеш.js, а його вже немає - 404 і зламана сторінка. Хорошою практикою є тримати файли попередньої збірки ще якийсь час. CDN з кешем може приховувати проблему: віддає старий файл зі свого кешу, поки той не витісниться; - динамічні частини (
import()) з тієї ж причини падають у давно відкритих вкладках - потрібна обробка помилки завантаження й пропозиція оновити сторінку; Varyне вказано для відповідей, що залежать від заголовків (Accept-Encoding, мова) - CDN може віддати не той варіант.
Шари кешування: кеш браузера, CDN (Cloudflare), зворотний проксі. Для CDN є окремі директиви: s-maxage (лише для спільних кешів) і stale-while-revalidate (віддати застаріле, поки оновлюється у фоні).
У Laravel + Vite файли з public/build/assets мають хеші - для них вебсервер (Nginx, Caddy) налаштовують з immutable. HTML формує Laravel, і для гостьових сторінок кешування на рівні CDN налаштовується окремо від статики.
Перевірка: вкладка Network у DevTools - колонка Size показує (disk cache)/(memory cache) чи розмір, а заголовки відповіді - реальну політику.
Core Web Vitals - три метрики Google, що описують досвід реального користувача. Вони впливають на ранжування в пошуку й показуються в Search Console.
| Метрика | Що вимірює | Добре |
|---|---|---|
| LCP (Largest Contentful Paint) | коли відмалювався найбільший видимий елемент: головне зображення, заголовок | до 2,5 с |
| INP (Interaction to Next Paint) | затримка від взаємодії (клік, натискання клавіші) до наступного відмальовування - найгірша з типових за весь візит | до 200 мс |
| CLS (Cumulative Layout Shift) | наскільки вміст «стрибає» під час завантаження | до 0,1 |
Оцінка - за 75-м перцентилем реальних відвідувань: сторінка «добра», якщо щонайменше 75% візитів укладаються в поріг. INP замінив FID у 2024 році: FID вимірював лише першу взаємодію й лише затримку до початку обробки.
Як вимірювати:
- польові дані (від реальних користувачів): звіт CrUX, Search Console, PageSpeed Insights, власний збір через бібліотеку
web-vitals:
import { onLCP, onINP, onCLS } from 'web-vitals';
const send = (metric) => navigator.sendBeacon('/vitals', JSON.stringify(metric));
onLCP(send);
onINP(send);
onCLS(send);
- лабораторні дані: Lighthouse, вкладка Performance у DevTools - відтворювані, але не замінюють польові (інший пристрій і мережа). INP у лабораторії майже не виміряти - немає реальних взаємодій.
Типові причини й виправлення:
LCP:
- головне зображення завантажується пізно -
fetchpriority="high", безloading="lazy"для першого екрана,preload; - повільна відповідь сервера (TTFB) - кешування сторінок, CDN;
- блокуючі CSS і JavaScript - менше критичного CSS,
deferдля скриптів; - шрифти, що затримують текст, -
font-display: swap.
INP:
- довгі задачі в головному потоці - розбити роботу (
scheduler.yield(),setTimeout), перенести обчислення у Web Worker; - важкі обробники подій - оновити інтерфейс одразу, а повільну роботу виконати після відмальовування;
- великий DOM і дорогі перерахунки стилів;
- сторонні скрипти (аналітика, чати, реклама).
CLS:
- зображення й відео без розмірів -
width/heightчиaspect-ratio; - вміст, що вставляється над наявним (банери, рекламні блоки), - зарезервувати місце;
- зміна шрифту після завантаження -
size-adjustдля запасного шрифту.
Для Laravel-сайту: Vite з розділенням коду, атрибути fetchpriority, width, height у Blade-компонентах зображень, Livewire wire:navigate для швидких переходів і кешування HTML на краю мережі для гостей.