Интересный факт: jQuery в настоящий момент используется на сайтах гораздо чаще, чем Flash.
jQuery — это удивительный инструмент, позволяющий разработчикам и дизайнерам работать с JavaScript без особых навыков . Однако, как учил нас Спайдермен, «с большой силой приходит большая ответственность». Главный недостаток jQuery в том, что несмотря на то, что он упрощает работу с JavaScript, все равно можно написать самый настоящий го#%!код. Скрипт, который будет тормозить загрузку страницы и увеличивать время отклика интерфейса, и будет запутан такими узлами спагетти, что следующему невезучему разработчику будет не обойтись без бутылки виски.
Задача становится еще сложнее для тех из нас, кто еще не переехал в волшебную сказочную страну чудес, где ни один из наших клиентов не использует для просмотра страниц Internet Explorer — скорость JavaScript движка IE сравнима со скоростью движения ледника, и не идет в сравнение с другими современными браузерами. Поэтому оптимизация производительности нашего кода становится делом еще более важным.
К счастью, существует несколько очень простых вещей, которые каждый может добавить в собственный рабочий процесс jQuery и решить множество базовых проблем. Анализируя различные образцы кода, я выделил три основных области, в которых мне постоянно встречаются самые серьезные проблемы: неэффективные селекторы, слабое делегирование событий и неуклюжая DOM манипуляция. Мы разберем каждую из них, и вы, я надеюсь, тоже извлечете пользу и сможете применить эти решения в своем следующем проекте.
Скорость селектора: высокая или низкая?
Сказать, что сила jQuery кроется в его способности выбирать DOM элементы и управлять ими — это то же самое, что утверждать, будто Photoshop — великолепный инструмент для выделения пикселей на экране и изменении из цвета. И то и другое — чудовищное упрощение, но факт остается фактом. jQuery дает нам множество способов выбора с каким элементом или элементами страницы мы хотим работать. Однако, удивительно большое число веб-разработчиков не знают, что селекторы не созданы идентичными; на самом деле, просто невероятно, насколько существенно может различаться производительность двух селекторов, которые на первый взгляд кажутся почти одинаковыми. К примеру, взглянем на эти два способа выбора всех тегов параграфов внутри <div> с помощью ID.
$("#id p");
$("#id").find("p");
Удивит ли вас то, что второй способ более чем в два раза быстрее первого? Знание того, какие селекторы (и почему) превосходят остальные в производительности, является идеальным строительным блоком уверенности в том, что код работает без проблем и не изматывает ваших пользователей необходимостью ожидать выполнения каждой команды.
Существует множество различных способов выборки элементов с использованием jQuery, но, среди наиболее распространенных, можно выделить пять методов. Выстроив их, строго говоря, в порядке от самого быстрого к самому медленному мы получим следующее:
$("#id");
Это, вне всяких сомнений, быстрейший селектор jQuery, он работает напрямую с исходным document.getElementbyld() методом JavaScript. По возможности, селекторы, следующие за выбранным, должны предваряться
$("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 должен проводиться лишь один раз. Как только результат селектора будет кэширован, вы сможете делать с ним что угодно.
Во-первых, начните свой поиск (здесь мы выбираем все элементы <li> внутри <ul id="blocks">):
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, мы можем привязать один обработчик к одному элементу (в данном случае <table>) и ждать событий в дочерних элементах. Чтобы сделать это с помощью метода .on(), достаточно внести лишь одно изменение в пример выше:
$(’table’).on(’click’,’td’,function() {
$(this).toggleClass(’active’);
});
Все, что мы сделали, это передвинули td-селектор в аргумент внутри метода .on(). Присоединив селектор к .on() мы перевели его в режим делегирования и теперь событие действует лишь на дочерние элементы нашего (table), которые соответствуют селектору (td). Благодаря этому простому изменению мы можем использовать всего один обработчик событий вместо сотни. Вы, наверное думаете, как здорово, что теперь браузеру придется выполнять в сто раз меньшую работу — и будете абсолютно правы. Разница между двумя примерами выше ошеломляюща.
(Заметьте, что если ваш сайт использует jQuery более ранней, нежели 1.7, версии, вы можете выполнить те же действия, используя метод .delegate(). Синтаксис вашего кода будет несколько отличаться от нашего; если вы никогда прежде не делали ничего подобного, вам стоит ознакомиться с API документацией, дабы разобраться, как это работает.)
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") на каждом этапе нашего цикла. Другой проблемой здесь является то, что при каждом повторении цикла он добавляет новый <li> к 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 к которой добавляется каждый новый из вновь создаваемых <li>. Когда наш цикл завершит работу, переменная tmp будет содержать в памяти весь список элементов и сможет быть приложена к нашему <ul> за один раз. С объектами в памяти браузеры работают намного быстрее, чем с теми, что на экране, поэтому данный метод построения списка много более быстрый и дружественный центральному процессору.
Это далеко не все способы улучшения вашего jQuery кода, но, определенно, одни из наиболее простых в применении. Хотя выигрыш от каждого индивидуального изменения составляет всего несколько миллисекунд, вместе они очень неплохо суммируются. Исследования показали, что человеческий глаз способен различать паузы длительностью от 100 мс, а значит, всего несколько изменений в коде могут легко изменить общее впечатление от скорости работы вашего сайта или приложения. У вас есть советы по работе с jQuery, которыми вы готовы поделиться? Оставьте их в комментариях и помогите нам всем стать лучше.
Теперь вперед, сделайте все на высшем уровне!
Оригинал статьи: http://24ways.org/2011/your-jquery-now-with-less-suck
Проведите конкурс среди участников CMS Magazine
Узнайте цены и сроки уже завтра. Это бесплатно и займет ≈5 минут.
Технический директор в «Далее»
По сути, канадец пишет в общем-то правильные вещи, но принимаясь что то оптимизировать, нужно понимать, что делать в первую очередь, и где нужна оптимизация, чтобы не устраивать «экономию на спичках». Для этого нужно пройтись профайлером и определить, какие участки кода тормозят больше всего; оптимизировать их и опять пройтись профайлером. Операцию повторить до достижения удовлетворительной скорости работы.
Вполне может оказаться, что основное время уходит не на поиск элементов, а на ожидание ответа сервера, или анимацию множества объектов, или отрисовку элементов браузером.
Скорость селектора: высокая или низкая?
здесь Скотт не объясняет, почему $("#id p") выполняется медленнее, чем $("#id").find("p"). Большинство разработчиков знает, что поиск по id выполняется быстро, и потому и указвают его в селекторе, т.к. уверены, что обе выше приведенные записи эквивалентны, но оказывается, что jQuery выполняет разбор и поиск элементов не в прямом направлении, а в обратном т.е. в нашей записи сначала jQuery найдет все абзацы, а потом для каждого из них будет искать среди предков элемент с id равным «id».
Манипуляции с DOM
Здесь, по сути, все написано верно, но опущен еще один важный способ оптимизации скорости вставки множества элементов в DOM дерево — использование documentFragment.