Про оптимизацию javascript кода (часть 1)
Нижеследующее — размышления после профилирования нескольких скриптов, и поисков быстрого кода.
Все больше статей по клиентской оптимизации появляется. Но проблема в том, что практики из статей начинают применять молодые разработчики без понимания причин. Зачастую авторы статей рассказывают как они решили свою проблему.
Видел людей, которые всерьез спорят про быстрые и медленные циклы, и использование бинарных операторов для ускорения. На практике потребность в подобных манипуляциях встречается очень редко. Эти споры процветают, пишутся тесты. Правда ребята забывают, что самым узким местом клиентского JS является DOM.
При переписывании цикла на более быструю версию, кеширование переменных (для более быстрого поиска по областям видимости) скрипт можно ускорить на 5%, ну на 10%. При этом произойдет
потеря в читаемости исходного кода, его понимании.
Но ситуация с оптимизацией — палка о трех концах. Второй конец: браузерная оптимизация. Например пример двух циклов:
// 1
for (var i = 0; i < arr.length; i += 1) {}
// 2
for (var i = 0, length = arr.length, i < length; i += 1){}
// 3
var length = arr.length;
while (length--){}
Вторая версия чуть более многословна, но быстрее первой. Третья менее многословна, но не совсем понятна слету (проход по массиву в обратном порядке не естественен для понимания).
На практике интерпретаторы сами оптимизируют код циклов, и большой разницы в скорости отработки в современном браузере профилирование не покажет, не говоря уже о пользовательских ощущениях. Какая разница, скрипт выполнится за 10мс или 9мс.
Подобные оптимизации сработают для старых браузеров. Но тут возникает вопрос целесообразности: надо ли держать такое количество непонятного кода только для оптимизации (еще не понятно необходимой ли) работы старых браузеров. Намного лучше оставить код многословным и однозначным. Пусть оптимизируют интерпретаторы.
А вот третий конец — это DOM. Обновление одного узла в DOM-e ведет за собой каскад телодвижений со стороны браузера: обновление свойств children, parentNode, nextSibling у нод, которые «цепляются» этим изменением, обновление liveCollections, триггер repaint и reflow, очистка памяти, если использован innerHTML, то еще дополнительный парсинг текста в DOM модель. На фоне такого количества действий низкоуровневые оптимизации меркнут. Слишком просто одним лишним обращением к DOM-у свести все оптимизации на нет.
Узких мест в DOM-е множество. Самые тяжелые — обновление: добавление и удаление узлов. При том время отработки одного и того же кода зависит от общей сложности DOM дерева, максимальной вложенности, количества таблиц стилей и количества правил. Это означает, что один и тот же способ оптимизации даст различный прирост, в зависимости от сложности страницы.
Браузерные производители понимают, и со своей стороны стараются оптимизировать код движков. И тут мы наблюдаем другую интересную ситуацию: разные подходы к оптимизации дают разный прирост в разных браузерах. Чем древнее браузер, тем вероятнее, что хитрые манипуляции дадут преимущество.
Снова повторяется история с циклами: не стоит усложнять исходны код, подстраивая его под браузер. Пусть этим занимаются браузеростроители.
Я к чему веду: смысла в особой оптимизации кода нет. Самый разумный подход — написать код как есть, структурировать и сделать его читаемым и понятным. И только если профилирование покажет необходимость оптимизаций, начать именно с оптимизации работы с DOM.