Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке polyfill?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Polyfill** - це JavaScript-код, який додає відсутній API браузера точно так, як визначає специфікація, без змін у коді застосунку. ```js if (!Array.prototype.includes) { Array.prototype.includes = function(el) { return this.indexOf(el) >= 0; }; } ``` **Ключове:** завжди перевіряй `if (!API)`, щоб не перезаписати нативну реалізацію.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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 року.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.