Skip to main content

Що таке polyfill?

Polyfill - це JavaScript-код, який реалізує стандартний API, відсутній у старому браузері, щоб сучасні можливості працювали так само, як якби браузер підтримував їх нативно.

Теорія

TL;DR

  • Polyfill - як перехідник для зарядки: конвертує інтерфейс так, щоб твій код підключився без жодних змін.
  • Polyfill додає відсутній API повністю. Shim виправляє зламану нативну реалізацію. Транспайлер на кшталт Babel переписує синтаксис.
  • Використовуй, коли підтримуєш браузери без ES6+ і білд-тул не покриває це автоматично.
  • Завжди перевіряй if (!API) перед визначенням, інакше перезапишеш нативний метод.

Швидкий приклад

js
// 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 після першого використання

js
arr.includes(2); // TypeError - методу ще немає if (!Array.prototype.includes) { /* polyfill */ } // запізно

Polyfill має виконатись до будь-якого коду, що викликає цей метод. Поклади його в entry-файл або підключи <script>-тегом перед основним бандлом.

Перезаписування нативного методу

js
// Без перевірки - завжди замінює, навіть у 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

js
// Без 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-компоненті

js
// 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 року.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?