Как применял полифилы на практике?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Практическое применение полифилов
Полифилы - это код, который обеспечивает функциональность в старых браузерах, которые не поддерживают новые возможности JavaScript. За 10+ лет я регулярно использовал их для поддержки старых версий браузеров.
1. Понимание полифилов
Полифилл - это кусок кода, который реализует функцию, которая браузер не поддерживает. Название происходит от "poly" (много) и "fill" (заполнять):
// Полифилл для Array.prototype.includes (IE11 не поддерживает)
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function(searchElement, fromIndex) {
if (this == null) {
throw new TypeError('Array.prototype.includes called on null or undefined');
}
const O = Object(this);
const len = parseInt(O.length) || 0;
if (len === 0) return false;
const n = parseInt(fromIndex) || 0;
let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x, y) {
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
}
while (k < len) {
if (sameValueZero(O[k], searchElement)) {
return true;
}
k++;
}
return false;
}
});
}
2. Полифилл для Object.assign
Эта функция критична для работы с объектами в старых браузерах:
// Полифилл для Object.assign (IE11)
if (typeof Object.assign !== 'function') {
Object.defineProperty(Object, 'assign', {
value: function assign(target, varArgs) {
'use strict';
if (target === null || target === undefined) {
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target);
for (let index = 1; index < arguments.length; index++) {
const nextSource = arguments[index];
if (nextSource !== null && nextSource !== undefined) {
for (const nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
// Использование в коде
const defaults = { timeout: 5000, retries: 3 };
const options = { timeout: 10000 };
const finalOptions = Object.assign({}, defaults, options);
console.log(finalOptions); // { timeout: 10000, retries: 3 }
3. Полифилл для Promise
Один из самых важных полифилов для старых браузеров:
// Упрощенный полифилл для Promise
if (typeof Promise === 'undefined') {
window.Promise = function(executor) {
let state = 'pending';
let value;
const handlers = [];
const resolve = (val) => {
if (state !== 'pending') return;
state = 'fulfilled';
value = val;
handlers.forEach(handle);
};
const reject = (reason) => {
if (state !== 'pending') return;
state = 'rejected';
value = reason;
handlers.forEach(handle);
};
const handle = (handler) => {
if (state === 'pending') {
handlers.push(handler);
} else {
if (state === 'fulfilled' && handler.onFulfilled) {
handler.onFulfilled(value);
}
if (state === 'rejected' && handler.onRejected) {
handler.onRejected(value);
}
}
};
executor(resolve, reject);
this.then = function(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
handle({
onFulfilled: (value) => {
try {
resolve(onFulfilled(value));
} catch (error) {
reject(error);
}
},
onRejected: (reason) => {
try {
reject(onRejected(reason));
} catch (error) {
reject(error);
}
}
});
});
};
};
}
4. Полифилл для Fetch API
Fetch был добавлен в браузеры позже XMLHttpRequest. Для IE11 нужен полифилл:
// Простой полифилл для fetch
if (!window.fetch) {
window.fetch = function(url, options) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const method = (options && options.method) || 'GET';
xhr.open(method, url);
// Установить headers
if (options && options.headers) {
Object.keys(options.headers).forEach(key => {
xhr.setRequestHeader(key, options.headers[key]);
});
}
xhr.onload = () => {
resolve({
ok: xhr.status >= 200 && xhr.status < 300,
status: xhr.status,
statusText: xhr.statusText,
text: () => Promise.resolve(xhr.responseText),
json: () => Promise.resolve(JSON.parse(xhr.responseText))
});
};
xhr.onerror = () => {
reject(new TypeError('Network request failed'));
};
xhr.send((options && options.body) || null);
});
};
}
5. Управление полифилами в проекте
На практике я использовал несколько подходов:
Подход 1: Условная загрузка
<!-- В HTML файле -->
<script>
// Проверяем, что нужно
if (!Array.prototype.includes) {
var script = document.createElement('script');
script.src = '/polyfills/array-includes.js';
document.head.appendChild(script);
}
if (!window.fetch) {
var script = document.createElement('script');
script.src = '/polyfills/fetch.js';
document.head.appendChild(script);
}
</script>
Подход 2: Использование polyfill.io
<!-- Сервис автоматически отправляет нужные полифилы -->
<script src="https://polyfill.io/v3/polyfill.min.js"></script>
Подход 3: Webpack и babel-polyfill
// webpack.config.js
module.exports = {
entry: ['@babel/polyfill', './src/index.js'],
// ...
};
// Или в .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
}
]
}
6. Реальный пример: поддержка IE11
На одном проекте нужно было поддерживать IE11. Я создал файл полифилов:
// polyfills.js
// Полифилл для Array.from
if (!Array.from) {
Array.from = (function() {
const toStr = Object.prototype.toString;
const isCallable = (fn) => typeof fn === 'function';
return function from(arrayLike, mapFn, thisArg) {
if (arrayLike == null) {
throw new TypeError('Array.from requires an array-like object');
}
const mapFnIsCallable = isCallable(mapFn);
const T = thisArg;
const len = Math.floor(Object(arrayLike).length);
const A = new Array(len);
let k = 0;
while (k < len) {
const kValue = Object(arrayLike)[k];
A[k] = mapFnIsCallable ? mapFn.call(T, kValue, k) : kValue;
k += 1;
}
A.length = len;
return A;
};
}());
}
// Полифилл для String.prototype.startsWith
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(search, pos) {
pos = !pos || pos < 0 ? 0 : +pos;
return this.substr(pos, search.length) === search;
};
}
// Полифилл для Object.entries
if (!Object.entries) {
Object.entries = function(obj) {
const entries = [];
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
entries.push([key, obj[key]]);
}
}
return entries;
};
}
7. Тестирование полифилов
// test-polyfills.js
describe('Polyfills', () => {
it('Array.includes should work', () => {
const arr = [1, 2, 3];
expect(arr.includes(2)).toBe(true);
expect(arr.includes(5)).toBe(false);
});
it('Object.assign should merge objects', () => {
const target = { a: 1 };
const source = { b: 2 };
const result = Object.assign(target, source);
expect(result).toEqual({ a: 1, b: 2 });
expect(result === target).toBe(true);
});
it('Promise should resolve correctly', (done) => {
const promise = new Promise((resolve) => {
resolve('success');
});
promise.then((result) => {
expect(result).toBe('success');
done();
});
});
});
Практические рекомендации
-
Проверьте требования браузеров - не добавляйте полифилы, если вам не нужно поддерживать старые браузеры
-
Используйте инструменты - babel с @babel/preset-env автоматически добавит нужные полифилы
-
Минимизируйте размер - полифилы увеличивают размер бандла. Загружайте их условно
-
Проверяйте производительность - полифилы могут быть медленнее нативной реализации
-
Документируйте - указывайте, какие полифилы используются и почему
-
Регулярно обновляйте - со временем старые браузеры отмирают, и полифилы можно удалить
Моя практика
Сейчас в большинстве проектов я:
- Использую babel с @babel/preset-env и опцией useBuiltIns: 'usage'
- Отказался от IE11 поддержки несколько лет назад (его доля < 1%)
- Для мобильных браузеров использую условную загрузку полифилов через feature detection
- Всегда измеряю размер бандла и убираю ненужные полифилы