Обеспечение совместимости JavaScript с разными браузерами

Раздел: Веб-разработка -> Совместимость

Основные подходы к обеспечению совместимости JavaScript

Совместимость JavaScript с браузерами – одна из ключевых задач веб-разработки. Разные версии браузеров поддерживают разные наборы возможностей языка и API. Для создания стабильно работающего кода применяются несколько стратегий.

Как определить, поддерживает ли браузер нужную функцию JavaScript?

Основной подход – обнаружение функций (feature detection). Вместо проверки строки user-agent программа проверяет наличие конкретного метода или свойства. Это надёжнее, так как не зависит от версии браузера.


// Проверка поддержки localStorage
if (typeof window.localStorage !== 'undefined') {
  localStorage.setItem('key', 'value');
} else {
  console.log('localStorage не поддерживается');
  // используйте резервный механизм, например куки
}
  

Javascript поддержка браузерами (поддержка javascript браузерами)

// Результат: в современных браузерах – запись в localStorage,
// в устаревших – сообщение в консоли.
  

Такой код выполняется без ошибок в любом браузере, а логика адаптируется под возможности.

Проблема:

Некоторые методы существуют, но работают некорректно (например, Array.prototype.includes может быть объявлен, но не соответствовать спецификации). Для таких случаев используют полифилы.

Библиотека Modernizr автоматизирует обнаружение многих функций, добавляя классы в <html>. Она генерируется под конкретный набор проверок.


// HTML: <html class="no-js">
// После загрузки Modernizr: <html class="js flexbox cssanimations">
// CSS: .no-flexbox .container { display: block; }
  

Как сделать код написанным на ES6+ работающим в старых браузерах?

Транспиляция с помощью Babel – наиболее распространённое решение. Код на современном JavaScript преобразуется в ES5, который поддерживается большинством браузеров.


// Исходный код (ES6):
const sum = (a, b) => a + b;

// После транспиляции Babel (ES5):
var sum = function sum(a, b) {
  return a + b;
};
  

Babel настраивается через .babelrc или babel.config.json, указываются цель (browserslist) и плагины/пресеты.


// Пример конфигурации Babel для поддержки IE11:
{
  "presets": [
    ["@babel/preset-env", {
      "targets": "> 0.25%, not dead"
    }]
  ]
}
  

Типичная ошибка:

Забыть про полифилы для новых глобальных объектов (Promise, Map, Set) или методов (Array.from, Object.assign). Babel по умолчанию преобразует только синтаксис. Нужно добавить core-js или отдельные полифилы через @babel/plugin-transform-runtime.

Также популярны сборщики (Webpack, Rollup), которые интегрируют Babel в процесс сборки.

Как загрузить разные скрипты в зависимости от возможностей браузера?

Условная загрузка скриптов через <script> с атрибутом type="module" и nomodule. Браузеры, поддерживающие ES-модули, выполнят модульный код; остальные – классический.


<script type="module" src="modern.js"></script>
<script nomodule src="legacy.js"></script>
  

Этот подход уменьшает размер загружаемого кода для современных браузеров, а для старых предоставляет полифилы и обратно совместимый код.

Проблема:

Некоторые старые браузеры (например, Safari 10) поддерживают модули, но не обрабатывают атрибут nomodule правильно. Необходимо дополнительное обнаружение.

Также можно использовать динамический импорт с проверкой:


if (typeof fetch !== 'undefined') {
  import('./feature.js').then(/*...*/);
} else {
  // fallback
}
  

Как проверить поддержку API в браузере без библиотек?

Написание собственных функций обнаружения. Это полезно для узких задач без использования Modernizr.


function supportsWebP() {
  const elem = document.createElement('canvas');
  if (elem.getContext && elem.getContext('2d')) {
    return elem.toDataURL('image/webp').indexOf('data:image/webp') === 0;
  }
  return false;
}
  
// Возвращает true в Chrome/Firefox (поддержка WebP),
// false в IE11 (нет метода toDataURL с WebP).
  

При обнаружении можно загружать альтернативные ресурсы.

Ошибка:

Проверка существования свойства, которое может быть объявлено, но не реализовано. Например, 'IntersectionObserver' in window может дать true, но объект не работает. Нужно вызывать конструктор и ловить ошибку.

Расширенные примеры обнаружения и полифилов

Ниже приведены подробные сценарии с пояснениями каждого шага и отображением результатов.

Пример 1: Полифил для Array.prototype.includes

Метод includes появился в ES2016. Старые браузеры (IE11, Safari 9) не поддерживают его. Полифил добавляет метод, если он отсутствует.

Пример

// Полифил (обычно из core-js)
if (!Array.prototype.includes) {
  Array.prototype.includes = function(searchElement, fromIndex) {
    if (this == null) {
      throw new TypeError('"this" is null or not defined');
    }
    var o = Object(this);
    var len = o.length >>> 0;
    if (len === 0) return false;
    var n = fromIndex | 0;
    var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
    while (k < len) {
      if (o[k] === searchElement) return true;
      k++;
    }
    return false;
  };
}

// Использование
const arr = [1, 2, 3];
console.log(arr.includes(2)); // true
console.log(arr.includes(4)); // false
// В браузере с поддержкой – выведет true и false.
// В старом браузере после полифила – то же самое.
// Если полифил не подключить, возникнет ошибка: arr.includes is not a function.

Пример 2: Обнаружение поддержки CSS Grid и запасной макет

Современные браузеры поддерживают CSS Grid Layout. Для старых применяется feature detection в CSS через @supports или JavaScript.

Пример

// JavaScript-проверка
if (typeof CSS !== 'undefined' && CSS.supports('display', 'grid')) {
  console.log('Grid поддерживается');
} else {
  console.log('Grid не поддерживается, используйте flexbox или float');
  document.body.classList.add('no-grid');
}
// В Chrome – 'Grid поддерживается'
// В IE11 – 'Grid не поддерживается, используйте flexbox или float'

Затем в CSS пишутся правила для .no-grid:

Пример

.layout {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
.no-grid .layout {
  display: flex;
}
.no-grid .layout > * {
  flex: 1;
}

Пример 3: Транспиляция с Babel и полифилами в проекте

Пошаговая настройка для сборки проекта на ES6 с поддержкой IE11.

  1. Установить Babel и пресет: npm install --save-dev @babel/core @babel/cli @babel/preset-env
  2. Установить core-js для полифилов: npm install core-js@3
  3. Файл конфигурации babel.config.json:
    Пример
    
    {
      "presets": [
        ["@babel/preset-env", {
          "targets": "> 0.25%, not dead",
          "useBuiltIns": "usage",
          "corejs": 3
        }]
      ]
    }
        
  4. Исходный файл src/main.js:
    Пример
    
    const promises = [Promise.resolve(1), Promise.resolve(2)];
    Promise.all(promises).then(console.log);
    
    const set = new Set([1,2,3]);
    console.log(set.has(1));
        
  5. Запуск транспиляции: npx babel src --out-dir dist
  6. Результат в dist/main.js (упрощённо):
    Пример
    
    "use strict";
    
    require("core-js/modules/es.array.iterator.js");
    require("core-js/modules/es.promise.js");
    
    var promises = [Promise.resolve(1), Promise.resolve(2)];
    Promise.all(promises).then(console.log);
    
    var set = new Set([1,2,3]);
    console.log(set.has(1));
        
// В IE11 код выполнится без ошибок, так как полифилы подставлены автоматически.
// Если бы использовалась только транспиляция (без полифилов), Promise и Set вызвали бы ошибку.

Пример 4: Использование сервис-воркера для условной загрузки полифилов

Сервис-воркер может перехватывать запросы и подставлять полифилы при необходимости. Это улучшает производительность.

Пример

// service-worker.js
self.addEventListener('fetch', event => {
  const url = new URL(event.request.url);
  // Проверка по заголовку User-Agent или через предварительное обнаружение
  // (упрощённый пример)
  if (url.pathname.endsWith('app.js')) {
    // Возвращаем версию с полифилами для старых браузеров
    event.respondWith(
      fetch('/polyfilled/app.js')
    );
  }
});
// Сервис-воркер регистрируется в обычном скрипте.
// Браузеры, поддерживающие Service Worker, будут получать оптимизированную версию.
// Для неподдерживающих – обычный скрипт загружается без изменений.

Поддержка JavaScript браузерами - comments

En
Javascript поддержка браузерами (javascript)