В старых IE, особенно в IE8 и ниже, ряд стандартных DOM-свойств не поддерживаются или поддерживаются плохо.
Если говорить о современных браузерах, то они тоже не все идут «в ногу», всегда какие-то современные возможности реализуются сначала в одном, потом в другом.
Но это не значит, что нужно ориентироваться на самый старый браузер из поддерживаемых!
Для того, чтобы не думать об устаревших браузерах, а писать современный код, который при этом работает везде, используют полифилы.
Полифилы
«Полифил» (англ. polyfill) – это библиотека, которая добавляет в старые браузеры поддержку возможностей, которые в современных браузерах являются встроенными.
Один полифил мы уже видели, когда изучали собственно JavaScript – это библиотека ES5 shim. Если её подключить, то в IE8- начинают работать многие возможности ES5. Работает она через модификацию стандартных объектов и их прототипов. Это типично для полифилов.
В работе с DOM несовместимостей гораздо больше, как и способов их обхода.
Что делает полифил?
Для примера добавим в DOM поддержку свойства firstElementChild
, если её нет. Здесь речь, конечно, об IE8, в других браузерах оно и так поддерживается, но пример типовой.
Вот код для такого полифила:
if (document.documentElement.firstElementChild === undefined) { // (1)
Object.defineProperty(Element.prototype, 'firstElementChild', { // (2)
get: function() {
var el = this.firstChild;
do {
if (el.nodeType === 1) {
return el;
}
el = el.nextSibling;
} while (el);
return null;
}
});
}
Если этот код запустить, то firstElementChild
появится у всех элементов в IE8.
Общий вид этого полифила довольно типичен. Обычно полифил состоит из двух частей:
- Проверка, есть ли встроенная возможность.
- Эмуляция, если её нет.
Проверка встроенного свойства
Для проверки встроенной поддержки firstElementChild
мы можем просто обратиться к document.documentElement.firstElementChild
.
Если DOM-свойство firstElementChild
поддерживается, то его значение не может быть undefined
. Если детей нет – свойство равно null
, но не undefined
.
Сравним:
alert( document.head.previousSibling ); // null, поддержка есть
alert( document.head.blabla ); // undefined, поддержки нет
За счёт этого работает проверка в первой строке полифила.
Важная тонкость – элемент, который мы тестируем, должен по стандарту поддерживать такое свойство.
Попытаемся, к примеру, проверить «поддержку» свойства value
. У input
оно есть, у div
такого свойства нет:
var div = document.createElement('div');
var input = document.createElement('input');
alert( input.value ); // пустая строка, поддержка есть
alert( div.value ); // undefined, поддержки нет
Если мы хотим проверить поддержку не свойства целиком, а некоторых его значений, то ситуация сложнее.
Например, нам интересно, поддерживает ли браузер <input type="range">
. То есть, понятно, что свойство type
у input
, в целом, поддерживается, а вот конкретный тип <input>
?
Для этого можно создать <input>
с таким type
и посмотреть, подействовал ли он.
Например:
<input type="radio">
<input type="no-such-type">
<script>
alert( document.body.children[0].type ); // radio, поддерживается
alert( document.body.children[1].type ); // text, не поддерживается
</script>
- Первый
input
имеетtype="radio"
. Этот тип точно поддерживается, поэтомуinput.type
имеет значение"radio"
, как и указано. - Второй
input
имеетtype="no-such-type"
. В качестве типа, для примера, специально указано заведомо неподдерживаемое значение. При этомinput.type
равен"text"
, таково значение по умолчанию. Мы можем прочитать его и увидеть, что поддержки нет.
Эта проверка работает, так как хоть в HTML-атрибут type
и можно присвоить любую строку, но DOM-свойство type
по стандарту хранит реальный тип input'а
.
Добавляем поддержку свойства
Если мы осуществили проверку и видим, что встроенной поддержки нет – полифил должен её добавить.
Для этого вспомним, что DOM элементы описываются соответствующими JS-классами.
Например:
<li>
– HTMLLiElement<a>
– HTMLAnchorElement<body>
– HTMLBodyElement
Они наследуют, как мы видели ранее, от HTMLElement, который является общим родительским классом для HTML-элементов.
А HTMLElement
, в свою очередь, наследует от Element, который является общим родителем не только для HTML, но и для других DOM-структур, например для XML и SVG.
Для добавления нужной возможности берётся правильный класс и модифицируется его prototype
.
Например, можно добавить всем элементам в прототип функцию:
Element.prototype.sayHi = function() {
alert( "Привет от " + this );
}
document.body.sayHi(); // Привет от [object HTMLBodyElement]
Сложнее – добавить свойство, но это тоже возможно, через Object.defineProperty
:
Object.defineProperty(Element.prototype, 'lowerTag', {
get: function() {
return this.tagName.toLowerCase();
}
});
alert( document.body.lowerTag ); // body
В IE8 современные методы для работы со свойствами, такие как Object.defineProperty, Object.getOwnPropertyDescriptor и другие не поддерживаются для произвольных объектов, но отлично работают для DOM-элементов.
Чем полифилы и пользуются, «добавляя» в IE8 многие из современных методов DOM.
Какова поддержка свойства?
А нужен ли вообще полифил? Какие браузеры поддерживают интересное нам свойство или метод?
Зачастую такая информация есть в справочнике MDN, например для метода remove()
: https://developer.mozilla.org/en-US/docs/Web/API/ChildNode.remove – табличка совместимости внизу.
Также бывает полезен сервис http://caniuse.com, например для elem.matches(css)
: http://caniuse.com/#feat=matchesselector.
Итого
Если вы поддерживаете устаревшие браузеры – и здесь речь идёт не только про старые IE, другие браузеры тоже обновляются не у всех мгновенно – не обязательно ограничивать себя в использовании современных возможностей.
Многие из них легко полифилятся добавлением на страницу соответствующих библиотек.
Для поиска полифила обычно достаточно ввести в поисковике "polyfill"
, и нужное свойство либо метод. Как правило, полифилы идут в виде коллекций скриптов.
Полифилы хороши тем, что мы просто подключаем их и используем везде современный DOM/JS, а когда старые браузеры окончательно отомрут – просто выкинем полифил, без изменения кода.
Типичная схема работы полифила DOM-свойства или метода:
- Создаётся элемент, который его, в теории, должен поддерживать.
- Соответствующее свойство сравнивается с
undefined
. - Если его нет – модифицируется прототип, обычно это
Element.prototype
– в него дописываются новые геттеры и функции.
Другие полифилы сделать сложнее. Например, полифил, который хочет добавить в браузер поддержку элементов вида <input type="range">
, может найти все такие элементы на странице и обработать их, меняя внешний вид и работу через JavaScript. Это возможно. Но если уже существующему <input>
поменять type
на range
– полифил не «подхватит» его автоматически.
Описанная ситуация нормальна. Не всегда полифил обеспечивает идеальную поддержку наравне с родными свойствами. Но если мы не собираемся так делать, то подобный полифил вполне подойдёт.
Один из лучших сервисов для полифилов: polyfill.io. Он даёт возможность вставлять на свою страницу скрипт с запросом к сервису, например:
<script src="//cdn.polyfill.io/v1/polyfill.js?features=es6"></script>
При запросе сервис анализирует заголовки, понимает, какая версия какого браузера к нему обратилась и возвращает скрипт-полифил, добавляющий в браузер возможности, которых там нет. В параметре features
можно указать, какие именно возможности нужны, в примере выше это функции стандарта ES6. Подробнее – см. примеры и список возможностей.
Также есть и другие коллекции, как правило, полифилы организованы в виде коллекции, из которой можно как выбрать отдельные свойства и функции, так и подключить всё вместе, пачкой.
Примеры полифилов:
- https://github.com/jonathantneal/polyfill – ES5 вместе с DOM
- https://github.com/termi/ES5-DOM-SHIM – ES5 вместе с DOM
- https://github.com/inexorabletash/polyfill – ES5+ вместе с DOM
Более мелкие библиотеки, а также коллекции ссылок на них:
- http://compatibility.shwups-cms.ch/en/polyfills/
- http://html5please.com/#polyfill
- https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills
Конечно, можно собрать и свою библиотеку полифилов самостоятельно из различных коллекций, которые перечислены выше, а при необходимости и написать самому. В этой части учебника мы изучим ещё много методов работы с DOM, которые в этом помогут.
Комментарии вернулись :)
<code>
, для нескольких строк кода — тег<pre>
, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)