150 раз
Таблица 6.3. Результаты выполнения тестов по определению видимости элементов. Времена приведены в миллисекундах. Взято среднее значение серии из 5 тестов
Судя по результатам тестов, доступ к offsetHeight медленнее в 50-150 раз.
Получается, что по отдельности и offsetHeight, и добавление элементов работают быстро, а вместе — очень медленно. Как же так?
Немного теории
Reflow — это процесс рекурсивного обхода ветви дерева DOM, вычисляющий геометрию элементов и их положение относительно родителя. Начало обхода — изменившийся элемент, но возможно и распространение в обратном порядке. Существуют следующие типы reflow:
начальный — первичное отображение дерева;
инкрементный — возникает при изменениях в DOM;
изменение размеров;
изменение стилей;
«грязный» — объединение нескольких инкрементных reflow, имеющих общего родителя.
Reflow делятся на неотложные (изменение размеров окна или изменение шрифта документа) и асинхронные, которые могут быть отложены и объединены впоследствии.
При манипулировании DOM происходят инкрементные reflow, которые браузер откладывает до конца выполнения скрипта. Однако исходя из определения reflow, «измерение» элемента вынудит браузер выполнить отложенные reflow. Т.к. возможно распространение снизу вверх, то выполняются все reflow, даже если измеряемый элемент принадлежит к неизменившейся ветви.
Операции reflow очень ресурсоемки и являются одной из причин замедления работы веб-приложений.
Если судить по тесту clean, все браузеры хорошо справляются с кэшированием многочисленных reflow. Однако запрашивая offsetHeight, мы «измеряем» элемент, что вынуждает браузер выполнить отложенные reflow. Таким образом, браузер делает тысячу reflow в одном случае и только один — в другом.
Замечание: в Opera reflow выполняется еще и по таймеру, что, однако, не мешает ей пройти тест быстрее остальных браузеров. Благодаря этому в Opera виден ход тестов — появляются добавляемые звездочки. Такое поведение оправдано, т.к. вызывает у пользователя ощущение большей скорости браузера.
Использование Computed Style
Что же показало тестирование? По меньшей мере, некорректно сравнивать универсальный (offsetHeight) и частный (style.display) случаи. Тестирование показало, что за универсальность надо платить. Если же все-таки хочется универсальности, то можно предложить другой подход: определение Computed Style — конечного стиля элемента (после всех CSS-преобразований).
getStyle = function()
{
var view = document.defaultView;
if(view && view.getComputedStyle)
return function getStyle(el,property)
{
return view.getComputedStyle(el,null)[property] ||
el.style[property];
};
return function getStyle(el,property)
{
return el.currentStyle && el.currentStyle[property] ||
el.style[property];
};
}();
Проведем тестирование этого способа и сведем все результаты в таблицу.
IE sp62
Firefox 2.0.0.12
Opera 9.22
Safari 3.04b
offsetHeight
23500
4453
4453
5140
style.display
171
56
56
34
getStyle
5219
5318
Таблица 6.4. Резульаты выполнения функции getStyle. Времена приведены в миллисекундах
Во-первых, для IE и Firefox (наиболее популярных браузеров) функция эта работает некорректно (в общем случае возвращает неверные данные). Во-вторых, работает она чуть ли не медленнее, чем offsetHeight.
Вообще говоря, рекомендуется не пользоваться такими универсальными функциями (getStyle есть практически в каждой JavaScript-библиотеке), а реализовывать необходимую функциональность в каждом конкретном случае. Ведь если мы договоримся, что скрытые элементы должны иметь класс hide, то все сведется к определению наличия этого класса у элемента или его родителей.
Оптимизация: определение класса hide
Давайте подробнее остановимся на предложенном мной решении. Предлагаю следующую реализацию:
function isHidden(el)
{
var p=el;
var b=document.body;
var re=/(^|\s)hide($|\s)/;
while(p && p!=b && !re.test(p.className))
p=p.parentNode;
return !!p && p!=b;
}
Предполагается, что корневые элементы DOM скрывать не имеет смысла и поэтому проверки ведутся только до document.body.
Предложенное решение явно не спустит лавину reflow, так как никаких вычислений и измерений не проводится. Однако немного смущает проход до корня документа: что же будет при большой вложенности элементов? Давайте проверим. Тест isHidden проводится для вложенности 2 (document.body / test_div), а тест isHidden2 — для вложенности 10 (document.body / div * 8 / test_div).