Junior: питання на співбесіді з теми «Браузерні API й мережа»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Усі три зберігають дані в браузері, але з різним терміном життя, обсягом і, головне, - різною поведінкою щодо сервера.
localStorage |
sessionStorage |
cookies | |
|---|---|---|---|
| живе | доки не видалять | доки відкрита вкладка | до Expires/Max-Age або до закриття браузера |
| видимість | усі вкладки домену | лише поточна вкладка | усі вкладки домену |
| обсяг | ~5 МБ на домен | ~5 МБ | ~4 КБ на cookie |
| іде на сервер | ні | ні | з кожним запитом |
| доступ з JS | так | так | так, якщо не HttpOnly |
Web Storage (localStorage, sessionStorage):
localStorage.setItem('theme', 'dark');
localStorage.getItem('theme'); // 'dark'
localStorage.removeItem('theme');
// лише рядки - об'єкти через JSON
localStorage.setItem('filters', JSON.stringify({ city: 'Київ' }));
const filters = JSON.parse(localStorage.getItem('filters') ?? '{}');
- зберігаються лише рядки:
setItem('count', 5)збереже'5'; - API синхронний - великі записи блокують головний потік;
- зміни в одній вкладці викликають подію
storageв інших вкладках того самого домену - спосіб синхронізувати вкладки; - у приватному режимі, при заповненому сховищі чи заблокованих cookies доступ може кидати виняток - звертання варто обгортати в
try.
Cookies створені для сервера: браузер автоматично додає їх до кожного запиту на домен. Тому в них зберігають ідентифікатор сесії, а не налаштування інтерфейсу - інакше кожен запит, включно з картинками, нестиме зайві байти.
Безпека - головне:
- не зберігайте токени автентифікації в
localStorage. Будь-який скрипт на сторінці (включно з XSS чи скомпрометованою бібліотекою) прочитає їх і відправить зловмиснику; - сесійна cookie з прапорцем
HttpOnlyнедоступна JavaScript взагалі - XSS не зможе її вкрасти. Так працює автентифікація Laravel (і Sanctum для SPA на тому ж домені); Secure- лише через HTTPS,SameSite- захист від підробки запитів з інших сайтів.
Що куди класти:
- тема, згорнуті панелі, чернетка форми -
localStorage; - стан майстра, що не повинен «перетікати» в іншу вкладку, -
sessionStorage; - сесія, автентифікація -
HttpOnly-cookie від сервера; - великі обсяги чи бінарні дані - IndexedDB (асинхронна база в браузері).
FormData збирає дані форми у формат, який браузер відправляє так само, як звичайна HTML-форма, - зокрема з файлами.
const form = document.querySelector('#profile-form');
form.addEventListener('submit', async (event) => {
event.preventDefault();
const data = new FormData(form); // усі поля з атрибутом name, включно з файлами
data.append('source', 'web'); // додаткове поле
const response = await fetch(form.action, {
method: 'POST',
body: data,
headers: { Accept: 'application/json' },
});
});
Найчастіша помилка - вручну задати Content-Type:
headers: { 'Content-Type': 'multipart/form-data' } // зламає запит
Для multipart/form-data заголовок обов'язково містить межу (boundary), що розділяє частини: multipart/form-data; boundary=----WebKitFormBoundary.... Браузер генерує її сам, якщо тіло - FormData. Вказаний вручну заголовок межі не має - сервер не зможе розібрати запит.
Корисні методи:
data.get('email'); // значення поля
data.getAll('tags[]'); // усі значення поля з однаковою назвою
data.set('email', 'new@example.com'); // замінити
data.delete('password_confirmation');
Object.fromEntries(data); // в об'єкт (для полів без повторів)
Файли: <input type="file" name="avatar"> потрапляє у FormData автоматично. Додати файл чи Blob вручну - data.append('avatar', file, 'avatar.png').
Особливості з Laravel:
PUT/PATCHз файлами: PHP історично розбираєmultipart/form-dataлише дляPOST. Для оновлення з файлами відправляютьPOSTз підміною методу:data.append('_method', 'PUT');- CSRF: для форм, що відправляються fetch-ем, токен з поля
_tokenпотрапить уFormDataсам, якщо в Blade-формі є@csrf; Accept: application/json- щоб Laravel при помилці валідації повернув JSON 422 з полемerrors, а не редирект назад;- масиви називають як у HTML:
tags[],items[0][qty]- Laravel розбере їх у вкладені масиви.
Коли не FormData: якщо файлів немає, а API приймає JSON, простіше body: JSON.stringify(payload) з Content-Type: application/json.
Прогрес завантаження великого файлу fetch наразі не показує. Для смуги прогресу використовують XMLHttpRequest (подія upload.onprogress) або бібліотеку на кшталт axios, що використовує його всередині.
Збирати адреси склеюванням рядків - джерело помилок: забуті ? і &, неекрановані пробіли, кирилиця й спецсимволи. Для цього є вбудовані класи.
URL - розбір і побудова адреси:
const url = new URL('https://laravelukraine.com/jobs?page=2#list');
url.hostname; // 'laravelukraine.com'
url.pathname; // '/jobs'
url.search; // '?page=2'
url.hash; // '#list'
// відносна адреса відносно базової
new URL('/api/users', window.location.origin).href;
URLSearchParams - параметри запиту:
const params = new URLSearchParams(window.location.search);
params.get('page'); // '2' - завжди рядок або null
params.has('q');
params.getAll('tags[]'); // для повторюваних параметрів
params.set('page', 3);
params.append('tags[]', 'php');
params.delete('page');
params.toString(); // 'tags%5B%5D=php'
Найзручніше - разом:
const url = new URL('/api/vacancies', location.origin);
url.searchParams.set('q', 'вакансії php');
url.searchParams.set('remote', '1');
await fetch(url); // '/api/vacancies?q=%D0%B2%D0%B0...&remote=1'
Екранування робиться автоматично: кирилиця, пробіли, & і = у значеннях не зламають адресу.
Побудова з об'єкта:
const query = new URLSearchParams({ q: 'laravel ukraine', tag: 'php&js' });
query.toString(); // 'q=laravel+ukraine&tag=php%26js'
Пробіл кодується як + (формат форм), а не %20 - сервери розуміють обидва варіанти.
Корисні прийоми:
- оновити адресу без перезавантаження (фільтри в каталозі):
const url = new URL(location.href);
url.searchParams.set('sort', 'price');
history.replaceState(null, '', url);
- перевірка коректності:
URL.canParse(value)- безtry, бо конструкторnew URLна некоректному рядку кидаєTypeError; - безпека: перед переходом за адресою з даних користувача варто перевірити
url.protocol- щоб не перейти наjavascript:....
Пастки:
params.getповертає рядок:params.get('page') + 1дасть'21'. Перетворюйте явно:Number(params.get('page') ?? 1);URLSearchParamsз об'єкта з масивом ({ ids: [1, 2] }) дастьids=1%2C2- для масивів потрібні окреміappend.
Laravel читає повторювані параметри як масив лише з дужками (tags[]=php&tags[]=js) - без них збережеться лише останнє значення.