Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

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.

Докладніше в документації: Web Workers API

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')));
  }
});

Життєвий цикл:

  1. install - підготовка: завантажити й закешувати потрібні файли;
  2. waiting - новий воркер чекає, доки закриються всі вкладки зі старим. Це захищає від ситуації, коли одна сторінка працює зі старими файлами, а запити обслуговує новий воркер;
  3. activate - новий воркер бере керування; тут видаляють старі кеші;
  4. fetch - перехоплення запитів.

self.skipWaiting() і clients.claim() прискорюють перемикання, але ціною можливої невідповідності версій.

Стратегії кешування:

  • cache first - для хешованих статичних файлів, що не змінюються;
  • network first - для HTML і API: свіже, а кеш лише як запас при відсутності мережі;
  • stale-while-revalidate - миттєво з кешу, паралельно оновити кеш з мережі.

Бібліотека Workbox реалізує ці стратегії й генерує воркер під час збирання (у Vite - vite-plugin-pwa).

Чим небезпечна помилка:

  • воркер живе довше за сторінку. Він встановлений у браузері користувача й перехоплює запити навіть після того, як ви виправили сайт. Якщо воркер помилково кешує HTML «назавжди», користувачі бачитимуть стару версію сайту, і деплой цього не виправить;
  • оновлення самого воркера - браузер перевіряє, чи змінився sw.js, при навігації на сайт (за замовчуванням в обхід HTTP-кешу, і щонайменше раз на 24 години). URL sw.js має лишатися незмінним: якщо перейменувати файл, старий воркер про новий так і не дізнається;
  • план відступу: заздалегідь мати «вимикач» - новий sw.js, що видаляє кеші й знімає реєстрацію (self.registration.unregister()).

Обмеження: лише HTTPS (виняток - localhost), без доступу до DOM, область дії (scope) - каталог, з якого віддано скрипт, і нижче.

Для звичайного сайту (не PWA) Service Worker часто зайвий: HTTP-кешування з хешованими іменами файлів дає більшу частину вигоди без ризиків.

Докладніше в документації: Service Worker API

Дві суперечливі вимоги: статичні файли мають кешуватися якомога довше (швидкість, трафік), а після деплою користувачі мають одразу отримати нову версію. Розв'язок - різна політика для 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) чи розмір, а заголовки відповіді - реальну політику.

Докладніше в документації: HTTP-кешування

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 на краю мережі для гостей.

Докладніше в документації: web.dev: Web Vitals