Що таке polyfill?
Polyfill - це JavaScript-код, який реалізує стандартний API, відсутній у старому браузері, щоб сучасні можливості працювали так само, як якби браузер підтримував їх нативно.
Теорія
TL;DR
- Polyfill - як перехідник для зарядки: конвертує інтерфейс так, щоб твій код підключився без жодних змін.
- Polyfill додає відсутній API повністю. Shim виправляє зламану нативну реалізацію. Транспайлер на кшталт Babel переписує синтаксис.
- Використовуй, коли підтримуєш браузери без ES6+ і білд-тул не покриває це автоматично.
- Завжди перевіряй
if (!API)перед визначенням, інакше перезапишеш нативний метод.
Швидкий приклад
// IE11 не має Array.prototype.includes
const arr = [1, 2, 3];
arr.includes(2); // TypeError в IE11
// Додай polyfill на початку entry-файлу
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement, fromIndex) {
return this.indexOf(searchElement, fromIndex) >= 0;
};
}
arr.includes(2); // true - тепер працює в IE11Перевірка if (!Array.prototype.includes) - це весь фокус. Якщо браузер уже має метод нативно, блок пропускається. Якщо ні - метод додається в ланцюжок прототипів, і кожен масив у цьому рантаймі автоматично його підхоплює.
Polyfill vs shim vs транспайлер
Ці три поняття часто плутають на співбесіді. Polyfill реалізує відсутній API повністю: з правильною сигнатурою методу, поверненими значеннями й крайніми випадками зі специфікації. Shim виправляє API, який існує, але працює неправильно або не повністю. Транспайлер на кшталт Babel переписує синтаксис під час білду (async/await в ланцюжки промісів, наприклад), але не додає runtime API.
Різниця важлива практично. Якщо Babel перетворить arr.includes() в arr.indexOf() >= 0 на рівні синтаксису, це не допоможе, коли includes справді відсутній у прототипі на момент виконання. Там потрібен polyfill.
Як це працює зсередини
Коли ти присвоюєш функцію до Array.prototype.includes, JS-рушій реєструє її як властивість об'єкта-прототипу Array. Будь-який виклик arr.includes() проходить по ланцюжку прототипів, знаходить твою функцію і викликає її. Перекомпіляції не відбувається. Polyfill патчить глобальний об'єкт у реальному часі, тому весь код у цьому рантаймі отримує одне визначення.
Саме тому важливий порядок: polyfill має бути зареєстрований до будь-якого виклику методу.
Коли використовувати
- Потрібна підтримка браузерів без певного API (спочатку перевір caniuse.com).
- Немає білд-тулу і Babel не підключений.
- Node.js нижче версії, де API вже є нативно, наприклад
Array.prototype.flatдо v11. - Вже використовуєш
@babel/preset-envзuseBuiltIns: 'usage'і core-js: в такому разі все підключається автоматично.
Для більшості нових проектів варто знати про polyfill.io. Це CDN-сервіс, який читає заголовок User-Agent і підставляє тільки ті polyfills, яких не вистачає конкретному браузеру.
Типові помилки
Polyfill після першого використання
arr.includes(2); // TypeError - методу ще немає
if (!Array.prototype.includes) { /* polyfill */ } // запізноPolyfill має виконатись до будь-якого коду, що викликає цей метод. Поклади його в entry-файл або підключи <script>-тегом перед основним бандлом.
Перезаписування нативного методу
// Без перевірки - завжди замінює, навіть у Chrome
Array.prototype.includes = function() { return true; }; // завжди true!Це ламає поведінку згідно зі специфікацією (ігнорує fromIndex, порушує обробку NaN) і прибирає JIT-оптимізований нативний варіант. Завжди огортай перевіркою if (!Array.prototype.includes).
Транспайл без polyfill
Babel переписує синтаксис, але fetch у IE11 все одно немає в рантаймі. Потрібні обидві речі: @babel/preset-env для синтаксису і polyfill на кшталт whatwg-fetch для API. useBuiltIns: 'usage' з core-js закриває обидві задачі одним кроком.
Власний polyfill з неправильною поведінкою
Мінімальний polyfill для Promise на базі setTimeout стакає виклики і падає зі stack overflow, бо не обробляє мікрозадачі так, як вимагає специфікація. Краще використовуй перевірені бібліотеки на кшталт core-js, а не писати реалізацію з нуля.
Де зустрічається в реальних проектах
- core-js: основа більшості Babel-конфігурацій. React-додатки з підтримкою IE11 підтягують його через
@babel/preset-env. - whatwg-fetch: використовувався в Create React App до 2018 року, досі зустрічається в legacy enterprise-проектах.
- polyfill.io: CDN-сервіс, що підставляє тільки потрібні polyfills залежно від User-Agent.
- React Native: полізаповнює
requestAnimationFrameдля Android нижче п'ятої версії.
Питання на співбесіді
Q: У чому різниця між polyfill і Babel?
A: Babel переписує синтаксис під час білду. Polyfill додає відсутні API під час виконання. Часто потрібні обидва одночасно.
Q: Чому polyfill може сповільнити додаток після завантаження?
A: Polyfill додає звичайну функцію до прототипу. JS-рушій не може застосувати до неї ті самі JIT-оптимізації, що до нативних методів. На більшості сценаріїв різниця мала, але на гарячих шляхах виконання вона помітна.
Q: Як підключити polyfills у сучасному проекті?
A: Додай core-js/stable в entry-файл і встанови useBuiltIns: 'usage' у @babel/preset-env. Це підключить тільки потрібні polyfills для цільових браузерів.
Q: У micro-frontend: що ламається, якщо два шели завантажують різні Promise polyfills?
A: Вони конфліктують на глобальному об'єкті. Один polyfill перезаписує Promise іншого, і ланцюжки .then() починають поводитись непередбачувано. Вирішення - спільний runtime або єдина точка завантаження polyfills до всіх micro-frontend.
Приклади
Базовий: polyfill для Array.prototype.includes
// Без polyfill - кидає помилку в IE11
const fruits = ['apple', 'banana', 'orange'];
fruits.includes('banana'); // TypeError: fruits.includes is not a function
// Додаємо polyfill перед першим використанням
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement, fromIndex) {
return this.indexOf(searchElement, fromIndex) >= 0;
};
}
// Тепер працює в будь-якому браузері
fruits.includes('banana'); // true
fruits.includes('grape'); // falseПеревірка if (!Array.prototype.includes) означає, що цей код виконується тільки в браузерах, де він потрібен. У Chrome або Firefox includes вже є нативно, і блок пропускається повністю.
Практичний: polyfill для fetch у React-компоненті
// fetch відсутній в IE11 і ранніх версіях Edge
// Цей патерн повторює принцип роботи whatwg-fetch
if (!window.fetch) {
window.fetch = function(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = () => {
if (xhr.status === 200) {
resolve({ json: () => JSON.parse(xhr.response) });
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = () => reject(new Error('Network error'));
xhr.send();
});
};
}
// React-компонент - працює в IE11 з polyfill вище
function UserList() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(r => r.json())
.then(setUsers);
}, []);
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// Результат: список користувачів рендериться в IE11 без помилокУ продакшені використовують повний пакет whatwg-fetch, а не цю спрощену версію. Патерн той самий: перевіряємо глобальний об'єкт, додаємо якщо відсутній, нативну реалізацію не чіпаємо. Таку схему я бачив у enterprise React-проектах, які підтримували IE11 до 2022 року.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.