Как сделать ваш скрипт jQuery на 67% эффективнее

jQuery – это удивительный инструмент, позволяющий разработчикам и дизайнерам работать с JavaScript без особых навыков . Однако, как учил нас Спайдермен, «с большой силой приходит большая ответственность». Главный недостаток jQuery в том, что несмотря на то, что он упрощает работу с JavaScript, все равно можно написать самый настоящий го#%!код. Скрипт, который будет тормозить загрузку страницы и увеличивать время отклика интерфейса, и будет запутан такими узлами спагетти, что следующему невезучему разработчику будет не обойтись без бутылки виски.

Задача становится еще сложнее для тех из нас, кто еще не переехал в волшебную сказочную страну чудес, где ни один из наших клиентов не использует для просмотра страниц Internet Explorer – скорость JavaScript движка IE сравнима со скоростью движения ледника, и не идет в сравнение с другими современными браузерами. Поэтому оптимизация сайта, а в частности производительности нашего кода становится делом еще более важным.

К счастью, существует несколько очень простых вещей, которые каждый может добавить в собственный рабочий процесс jQuery и решить множество базовых проблем. Анализируя различные образцы кода, я выделил три основных области, в которых мне постоянно встречаются самые серьезные проблемы: неэффективные селекторы, слабое делегирование событий и неуклюжая DOM манипуляция. Мы разберем каждую из них, и вы, я надеюсь, тоже извлечете пользу и сможете применить эти решения в своем следующем проекте.

Скорость селектора: высокая или низкая?


Сказать, что сила jQuery кроется в его способности выбирать DOM элементы и управлять ими – это то же самое, что утверждать, будто Photoshop – великолепный инструмент для выделения пикселей на экране и изменении из цвета. И то и другое – чудовищное упрощение, но факт остается фактом. jQuery дает нам множество способов выбора с каким элементом или элементами страницы мы хотим работать. Однако, удивительно большое число веб-разработчиков не знают, что селекторы не созданы идентичными; на самом деле, просто невероятно, насколько существенно может различаться производительность двух селекторов, которые на первый взгляд кажутся почти одинаковыми. К примеру, взглянем на эти два способа выбора всех тегов параграфов внутри
с помощью ID.

Код

$("#id p");
$("#id").find("p");


Удивит ли вас то, что второй способ более чем в два раза быстрее первого? Знание того, какие селекторы (и почему) превосходят остальные в производительности, является идеальным строительным блоком уверенности в том, что код работает без проблем и не изматывает ваших пользователей необходимостью ожидать выполнения каждой команды.

Существует множество различных способов выборки элементов с использованием jQuery, но, среди наиболее распространенных, можно выделить пять методов. Выстроив их, строго говоря, в порядке от самого быстрого к самому медленному мы получим следующее:

Код
$(“#id”);


Это, вне всяких сомнений, быстрейший селектор jQuery, он работает напрямую с исходным document.getElementbyld() методом JavaScript. По возможности, селекторы, следующие за выбранным, должны предваряться ID-селектором в сочетании с методом jQuery .find(), дабы ограничить объем страницы, на которой должен вестись поиск (как и в случае $(“#id”).find(“p”) продемонстрированным выше).

Код

$(“p”); , $(“input”); , $(“form”); и тому подобные


Также быстро выбирают элементы по тегу имени, так как метод ссылается на оригинальный document.getElementsByTagname().

Код

$(“.class”);


Выбор по имени класса немного более сложен. Хотя он до сих пор достаточно хорошо выполняется в современных браузерах, метод может вызвать значительное замедление работы IE8 и ниже. Почему? IE9 был первой версией IE поддерживавшей родной JavaScript метод document.getElementsByClassName(). Старым браузерам приходится прибегать к гораздо более медленному методу DOM-поиска, способному значительно ухудшить производительность.

Код

$(“[attribute=value]“);


Для этого селектора не существует родного метода JavaScript, поэтому единственный способ, которым jQuery может его выполнить – проползти через весь DOM в поисках совпадений. Современные браузеры, которые поддерживают метод querySelectorAll(), в ряде случаев сделают это чуть лучше (Opera, по сравнению с другими, выполняет этот вид поиска в особенности быстро), но, говоря начистоту, этот селектор просто Медлен Медленовский.

Код

$(“:hidden”);


Как селектору атрибутов, ему не соответствует ни один из родных методов JavaScript. Псевдоселекторы могут быть мучительно медленными, так как селектор должен обследовать каждый элемент в выделенном пространстве поиска. Опять же, современные браузеры с querySelectorAll() могут делать это чуть лучше, но постарайтесь все же избегать его по возможности. Если же вам без него не обойтись, попробуйте хотя бы ограничить зону поиска до определенного участка страницы с помощью: $(“#list”).find(“:hidden”);

Но – эй! – все доказывается тестами производительности, ведь так? Сравните селекторы классов в IE7 или 8 с остальными браузерами, а потом удивляйтесь, как люди из Microsoft, работающие над IE, могут спокойно спать по ночам…

Цепочки


Практически все методы jQuery возвращают jQuery объекты. Это означает, что во время выполнения метода его результаты возвращаются, и вы можете продолжить проводить над ними большее число операций. Вместо написания одного и того же селектора несколько раз, просто выберите несколько действий, которые необходимо произвести с ним.

Без цепочек

Код

$("#object").addClass("active");
$("#object").css("color","#f0f");
$("#object").height(300);


С цепочками

Код

$("#object").addClass("active").css("color", "#f0f").height(300);


Получаем двойной эффект – ваш код становится одновременно короче и быстрее. Объединенные в цепочки методы будут чуть быстрее, чем множественные, проводимые с кэшированным селектором, и оба будут много более быстрыми, нежели множественные методы, проводимые с некэшированными селекторами. Подождите… «Кэшированные селекторы»? Что это за дьявольщина?

Кэширование


Другой простой способ (который, по всей видимости, тоже является тайной для разработчиков) ускорить работу вашего кода – это идея кэширования селекторов. Подумайте о том, как часто вам порой приходилось писать один и тот же селектор снова и снова в своем проекте. Каждый селектор $(“.element”) должен обыскивать весь DOM каждый раз снова, не зависимо от того, сколько раз он был запущен до этого. Проведение выборки один раз и сохранение полученного результата в переменной означает, что поиск в DOM должен проводиться лишь один раз. Как только результат селектора будет кэширован, вы сможете делать с ним что угодно.

Во-первых, начните свой поиск (здесь мы выбираем все элементы
  • внутри
      ):

      Код

      var blocks = $("#blocks").find("li");


      Теперь вы можете использовать переменную blocks где угодно без необходимости обыскивать DOM каждый раз.

      Код

      $("#hideBlocks").click(function() {
      blocks.fadeOut();
      });
      $("#showBlocks").click(function() {
      blocks.fadeIn();
      });


      Мой совет? Любой селектор, выполняемый более одного раза, должен быть кэширован. Этот jsperf тест демонстрирует, насколько быстро работает кэшированный селектор по сравнению с некэшированным (и вдобавок демонстрирует работу цепочек).

      Делегирование событий
      Даже обработчики событий занимают память. В сложных веб-сайтах и приложениях довольно часто можно видеть множество обработчиков и, к счастью, jQuery предоставляет ряд по-настоящему простых методов для эффективной обработки их путем делегирования.

      Немного экзотичный пример – представьте себе ситуацию, когда таблица 10х10 ячеек должна иметь обработчик событий в каждой из них. Предположим, что щелчок по ячейке прибавляет или отнимает класс, определяющий цвет ее фона. Типичный метод, которым это может быть осуществлено (и с которым мне часто приходилось встречаться в образцах) выглядит следующим образом:

      Код

      $('table').find('td').click(function() {
      $(this).toggleClass('active');
      });


      jQuery 1.7 предоставляет нам новый метод – обработчик событий .on(). Он действует как утилита, которая объединяет все предыдущие обработчики событий в один удобный метод, и то, как вы его вписываете, определяет, как он должен себя вести. Чтобы переписать .click() в примере выше с использованием .on(), мы просто должны сделать следующее:

      Код

      $('table').find('td').on('click',function() {
      $(this).toggleClass('active');
      });


      Довольно просто, правда? Конечно, но проблема здесь в том, что мы по-прежнему привязываем сто обработчиков к одной странице, по одному для каждой из ячеек таблицы. Гораздо лучшим способом было бы создание единственного обработчика событий в таблице, который следил бы за всеми событиями внутри нее. Так как большая часть событий охватывает дерево DOM, мы можем привязать один обработчик к одному элементу (в данном случае ) и ждать событий в дочерних элементах. Чтобы сделать это с помощью метода .on(), достаточно внести лишь одно изменение в пример выше:

      Код

      $('table').on('click','td',function() {
      $(this).toggleClass('active');
      });


      Все, что мы сделали, это передвинули td-селектор в аргумент внутри метода .on(). Присоединив селектор к .on() мы перевели его в режим делегирования и теперь событие действует лишь на дочерние элементы нашего (table), которые соответствуют селектору (td). Благодаря этому простому изменению мы можем использовать всего один обработчик событий вместо сотни. Вы, наверное думаете, как здорово, что теперь браузеру придется выполнять в сто раз меньшую работу – и будете абсолютно правы. Разница между двумя примерами выше ошеломляюща.

      (Заметьте, что если ваш сайт использует jQuery более ранней, нежели 1.7, версии, вы можете выполнить те же действия, используя метод .delegate(). Синтаксис вашего кода будет несколько отличаться от нашего; если вы никогда прежде не делали ничего подобного, вам стоит ознакомиться с API документацией, дабы разобраться, как это работает.)

      Манипуляции с DOM
      jQuery позволяет легко манипулировать DOM. Очень просто создавать новые узлы, вставлять одни, удалять другие, перемещать их и так далее. Хотя сам код пишется легко, во время каждой манипуляции с DOM браузер должен перерисовывать и перегруппировывать контент, что может быть весьма ресурсоемким процессом. В особенности это касается длинных циклов, будь то стандартный цикл for(), цикл while() или jQuery цикл $.each().

      Предположим, что мы только что получили массив, переполненный URL ссылками на изображения из базы данных или Ajax-вызов, или что угодно – и мы хотим поместить все эти изображения в неупорядоченный список. Обычно вы видите код вроде этого:

      Код

      var arr = [reallyLongArrayOfImageURLs];
      $.each(arr, function(count, item) {
      var newImg = '<li><img src="'+item+'"></li>';
      $('#imgList').append(newImg);
      });


      Но здесь есть несколько проблем. Для начала (и вы, вероятно, уже заметили это – если внимательно читали статью) мы делаем выборку через $(“#imgList”) на каждом этапе нашего цикла. Другой проблемой здесь является то, что при каждом повторении цикла он добавляет новый
    • к DOM. Каждая из этих итераций дорого нам обойдется и, если наш массив достаточно велик, это может привести к серьезному замедлению работы или даже зловещему предупреждению “A script is causing this page to run slowly” («Скрипт замедляет работу страницы»).

      Код

      var arr = [reallyLongArrayOfImageURLs],
      tmp = '';
      $.each(arr, function(count, item) {
      tmp += '<li><img src="'+item+'"></li>';
      });
      $('#imgList').append(tmp);


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