Пару недель назад я прочитал замечательную книгу на английском — High Performance JavaScript от Николаса Закаса (Nickolas C. Zakas). Книга очень хорошая и добросовестно написана. Николас рассказывает об интересных деталях уже известных технологий и делится неожиданными техниками оптимизации. А так как книга о производительности, то конечно же есть графики и сравнения, но не всё же книга написана живым языком. Интересно читать всю книгу целиком. Также книга открывает перед тобой некоторые аспекты производительности веб-страницы о которых ты раньше не задумывался или даже не знал.

Книга разделена на 10 глав. В первой главе рассказывается про загрузку и выполнение джаваскрипта. Из этой главы важно вынести, что загрузка и выполнение джаваскриптовых файлов являются блокируюшими процессами и значит, в то время пока выполняется джаваскрипт не может идти процесс рендеринга страницы. Отсюда напрашивается вывод, что все js-файлы следует подключать в самом низу страницы перед закрывающим тегом </body>, а аспект загрузки побуждает нас минифицировать, конкатенировать все файлы, и также было бы неплохо это всё отдавать с сервера с помощью gzip.

Во второй главе Николас раскрывает вопрос цены обращения к данным и переменным. Как вы уже можете догадаться, если вы будете кешировать внешние (по отношению к текущему месту выполнению) переменные в локальные, и вообще как только вы начнёте кешировать переменные, всё начнёт работать быстрее.

Затем Закас объясняет, что операции с DOM-деревом чрезвычайно дорогие и по возможности минимизируйте их. Кешируйте DOM-элементы в локальные переменные. Старайтесь не работать напрямую с DOM-деревом, по возможно склонируйте его или скопируйте с помощью document.createDocumentFragment() сделайте с элементами всё, что вам нужно, а после поместите обратно в DOM — таким образом вы уменьшите количество событий repaint и reflow. Для обращения к переменным также старайтесь использовать последние API (document.querySelector() и document.querySelectorAll()). В этой главе я особенно восхищался методом document.createDocumentFragment() и наконец обретённым знанием, чем же отличается reflow от repaint — reflow выполняется, когда меняется геометрия страницы (выполнятеся сразу после того, как были изменены какие-либо свойства, отразившиеся на размерах), а repaint — простой ререндеринг участка. Следующим закономерным открытием было, что браузеры пытаются оптимизировать эти два события и кешируют их, поэтому не стоит сбрасывать этот кеш каждый раз обращаясь к свойствам (offsetTop|Right|Bottom|Left, scrollTop|Right|…, clientTop|Right|…, getComputedStyle()) для вычисления которых, браузеру придётся выполнить весь стек закешированных repaint и reflow — и соответственно, все оптимизации будут напрасны.

После этого рассказывается, что самый быстрый цикл — это старый добрый for. И также абсолютно очевидные советы, которым всё равно часто не хватает осознаности при повседневном применении: «Чтобы увеличить производительность цикла старайтесь уменьшить количество итераций и количество операций внутри одной итерации».

В пятой главе идёт речь о регулярных выражениях и о том, что не стоит их бояться и о том, как использовать их эффективно. Сами по себе регулярные выражения хороши тем, что оптимизированы на более низком уровне и поэтому работают быстрее любых других текстовых операций. Но как и у любого другого инструмента у них есть область применения в которой регулярные выражени особенно хороши. Но в контексте производительности нам важны другие правила, которые заставят их работать быстрее. Старайтесь писать регулярку так, чтобы она имела больший шанс закончить выполнение, вместо медленного выполнения остальных проверок. Начинайте регулярку с простого и необходимого токена, чтобы при первой же проверке было ясно имеет ли смысл дальнейшие проверки или нет.

Затем рассказывается про отзывчивые интерфейсы — интерфейсы без зависания и с хорошим откликом — о том как их добиться. Вся штука в том, что выполнение джаваскрипта как уже было выяснено раньше является блокирующим процессом, поэтому интерфейс не может реагировать на действия пользователя во время выполнения джаваскрипта и наоборот. Так как критическим откликом являются 100 миллисекунд, то ваша задача рефакторить ваш код в соответствии с этим условием. В вашем распоряжении отложенное выполнение с помощью таймеров и плюсом ещё одна невероятная вешь — webworkers – эта технология позволяет вынести продолжительные операции в отдельный поток и поэтому эти операции перестают блокировать интерфейс.

Также в книге поднимаются темы мемоизации функций, кеширования результатов, оптимизирующего переопределения функции. Об этом можно рассказать подробнее, допустим у вас есть некроссбраузерный метод, для которого вы пишете обёртку:

function addHandler(target, eventType, handler) { if (target.addEventListener) { // DOM2 Events target.addEventListener(eventType, handler, false); } else { // ugly IE target.attachEvent("on" + eventType, handler); } } 

Но мы ясно видим, что каждый раз при назвачении обработчика будет выполняться одно и тоже условие, и так как вряд-ли в пределах одной страницы и одной сессии пользователь будет менять браузер, то мы можем создать следующую оптимизацию:

function addHandler(target, eventType, handler) { // выполним проверку один раз и перезапишем саму функцию, // и после этого все следующие разы функция будет работать быстрее // так как избавилась от постоянного условия внутри if (target.addEventListener) { // DOM2 Events addHandler = function(target, eventType, handler) { target.addEventListener(eventType, handler, false); } } else { // ugly IE addHandler = function(target, eventType, handler) { target.attachEvent("on" + eventType, handler); } } // вызовем функцию, чтобы сразу переопределить addHandler(target, eventType, handler); } 

Итогом всей книги будет несколько простых истин, которые гласят: чтобы увеличть производительность нужно:

  • уменьшать количество итераций в циклах.
  • Уменьшать количество операций в каждой отдельной итерации.
  • Используйте локальные переменные.
  • Кешируйте всё, что возможно.
  • Избегайте дорогих операций.
  • Аккуратно используйте регулярки.
  • Поменьше трогайте DOM-дерево.
  • А если трогаете, то сведите количество операции к минимуму.
  • Не сбрасывайте браузерные кеши на repaint и reflow.
  • Выучите битовые операторы — они правда быстрые.
  • Пишите меньше кода.
Редактировать / Начать обсуждение