Frontender Magazine

Пишем качественный код на jQuery

шапка

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

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

Во-вторых, я настойчиво рекомендую всем новичкам, которые только начали осваиваться с JavaScript, перед тем, как связываться с jQuery, прочитать следующие статьи: о передовых JavaScript-практиках для начинающих, и о том, как писать качественный JavaScript-код.

Когда вы будете готовы использовать jQuery, я настоятельно советую вам придерживаться нижеследующих принципов:

Кэшируйте выборку в переменных

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

// плохо
h = $('#element').height();
$('#element').css('height',h-20);

// хорошо
$element = $('#element');
h = $element.height();
$element.css('height',h-20);

Не используйте глобальные переменные

Для jQuery, как и для JavaScript, лучше всего делать так, чтобы переменные были замкнуты внутри функций, в которых они используются.

// плохо
$element = $('#element');
h = $element.height();
$element.css('height',h-20);

// хорошо
var $element = $('#element');
var h = $element.height();
$element.css('height',h-20);

Используйте венгерскую нотацию

Если говорить простыми словами, венгерская нотация1 в jQuery — это когда в начале переменной стоит символ доллара, и вам легко сразу понять, что эта переменная содержит jQuery-объект.

// плохо
var first = $('#first');
var second = $('#second');
var value = $first.val();

// хорошо - перед объектами, которые управляются jQuery, мы ставим символ $
var $first = $('#first');
var $second = $('#second'),
var value = $first.val();

Используйте цепочки переменных (паттерн одного ‘var’)

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

var
  $first = $('#first'),
  $second = $('#second'),
  value = $first.val(),
  k = 3,
  cookiestring = 'SOMECOOKIESPLEASE',
  i,
  j,
  myArray = {};

Используйте ‘On’

Последние версии библиотеки jQuery привнесли изменения в функции типа click() — теперь это сокращение от on('click'). В более ранних версиях click() являлся сокращением от bind(). Начиная с версии jQuery 1.7 предпочтительный метод для привязки обработчиков событий — on(). Для единообразия гораздо проще использовать on() везде в подобных случаях.

// плохо
$first.click(function(){
    $first.css('border','1px solid red');
    $first.css('color','blue');
});

$first.hover(function(){
    $first.css('border','1px solid red');
})

// лучше
$first.on('click',function(){
    $first.css('border','1px solid red');
    $first.css('color','blue');
})

$first.on('hover',function(){
    $first.css('border','1px solid red');
})

Концентрированный JavaScript

В общем, там, где есть для этого возможность, лучше совмещать несколько функций в одну.

// плохо
$first.click(function(){
    $first.css('border','1px solid red');
    $first.css('color','blue');
});

// лучше
$first.on('click',function(){
    $first.css({
        'border':'1px solid red',
        'color':'blue'
    });
});

Используйте цепочки

Библиотека jQuery позволяет вам очень просто связывать методы в цепочки. Пользуйтесь этим!

// плохо
$second.html(value);
$second.on('click',function(){
    alert('hello everybody');
});
$second.fadeIn('slow');
$second.animate({height:'120px'},500);

// лучше
$second.html(value);
$second.on('click',function(){
    alert('hello everybody');
}).fadeIn('slow').animate({height:'120px'},500);

Оставляйте код читаемым

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

// плохо
$second.html(value);
$second.on('click',function(){
    alert('всем привет');
}).fadeIn('slow').animate({height:'120px'},500);

// лучше
$second.html(value);
$second
    .on('click',function(){ alert('всем привет');})
    .fadeIn('slow')
    .animate({height:'120px'},500);

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

Сокращенное вычисление означает, что выражения оцениваются слева направо с использованием операторов && (логическое и) и || (логическое или).

// плохо
function initVar($myVar) {
    if(!$myVar) {
        $myVar = $('#selector');
    }
}

// лучше
function initVar($myVar) {
    $myVar = $myVar || $('#selector');
}

Сокращайте!

Один из способов сделать код более компактным — воспользоваться сокращениями.

// плохо
if(collection.length > 0){..}

// лучше
if(collection.length){..}

Отделяйте элементы, когда нужно провести с ними ресурсоёмкие операции

Если вы собираетесь провести ряд ресурсоёмких операций над элементом DOM, рекомендуется отделить его от документа, а потом добавить снова.

// плохо
var
    $container = $("#container"),
    $containerLi = $("#container li"),
    $element = null;

$element = $containerLi.first();
//... много сложных манипуляций

// лучше
var
    $container = $("#container"),
    $containerLi = $container.find("li"),
    $element = null;

$element = $containerLi.first().detach();
//... много сложных манипуляций

$container.append($element);

Знайте особенности

Если вы используете jQuery-методы, с которыми у вас не так много опыта работы, не поленитесь предварительно прочесть документацию: вполне возможно, для вашей задачи есть заранее продуманный или более оптимальный вариант решения.

// плохо
$('#id').data(key,value);

// лучше (быстрее)
$.data('#id',key,value);

Кэшируйте родительские элементы для подзапросов

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

// плохо
var
    $container = $('#container'),
    $containerLi = $('#container li'),
    $containerLiSpan = $('#container li span');

// лучше (и быстрее)
var
    $container = $('#container '),
    $containerLi = $container.find('li'),
    $containerLiSpan= $containerLi.find('span');

Не используйте универсальный селектор

Универсальный селектор в совокупности с другими селекторами — это очень медленная выборка.

// плохо
$('.container > *');

// лучше
$('.container').children();

Вместо подразумеваемого универсального селектора, пишите конкретные селекторы

Если вы не указываете никакого селектора, по умолчанию подставляется универсальный селектор (*).

// плохо
$('.someclass :radio');

// лучше
$('.someclass input:radio');

Оптимизируйте селекторы

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

// плохо
$('div#myid');
$('div#footer a.myLink');

// лучше
$('#myid');
$('#footer .myLink');

Не используйте в запросе путь из нескольких id

Еще раз повторюсь, если вы используете id правильно, то одного id вполне достаточно, и дополнительная специфичность в запросе из нескольких вложенных id селекторов не требуется.

// плохо
$('#outer #inner');

// лучше
$('#inner');

Старайтесь использовать последнюю версию библиотеки

Чаще всего новая версия является лучшей по сравнению с предыдущей. Обычно она легче или, например, быстрее. Однако не стоит забывать об обратной совместимости и поддержки кода. Например, версия библиотеки jQuery 2.0 не поддерживает версии браузера Internet Explorer 6, 7 и 8.

Не используйте устаревшие методы

Всегда смотрите на список устаревших методов в каждой новой версии и старайтесь их не использовать в вашем коде.

// плохо - метод live является устаревшим
$('#stuff').live('click', function() {
  console.log('ура!');
});

// лучше
$('#stuff').on('click', function() {
  console.log('ура!');
});

Загружайте jQuery с CDN

Наиболее быстрый способ передать пользователю скрипт с ближайшего к нему сервера — использовать технологию CDN от Google. Чтобы использовать CDN Google, используйте в коде следующий адрес: http://code.jquery.com/jquery-latest.min.js

Старайтесь совмещать jQuery и нативный JavaScript

Как я говорил выше, jQuery — это не что иное, как JavaScript, а, значит, на jQuery мы всего лишь делаем те же самые вещи, которые могли бы делать и на встроенном в браузер JavaScript. Конечно же, когда вы пишете на нативном («ванильном») JavaScript, это зачастую приводит к длинным файлам с нечитаемым кодом, который сложно поддерживать. Однако, это также означает и то, что ваш код будет исполняться быстрее. Помните, что нет такого js-фреймворка, который был бы легче в исполнении для браузера, чем операции на нативном JavaScript.

jq

(Кликните на картинку и проверьте)

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

Напутствие напоследок

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

Не забывайте также, что использование jQuery – это выбор, а не требование. Подумайте, зачем вы вообще используете jQuery. Для преобразований DOM? AJAX? Шаблонов? CSS-анимаций? Как движок селекторов? Возможно, для ваших задач имеет смысл использовать микрофреймворки на JavaScript или какую нибудь кастомную сборку jQuery, которая будет четко отвечать требованиям вашего проекта.


Примечания

1.Венге́рская нота́ция в программировании — соглашение об именовании переменных, констант и прочих идентификаторов в коде программ. Своё название венгерская нотация получила благодаря программисту компании Microsoft венгерского происхождения Чарльзу Симони, предложившему её ещё во времена разработки первых версий MS-DOS. Эта система стала внутренним стандартом Майкрософт. Подробнее о ней вы можете прочесть в Википедии.

Если вы заметили ошибку, вы всегда можете отредактировать статью, создать issue или просто написать об этом Антону Немцеву в skype ravencry.

Mathew Carella
Vlad Andersen

Комментарии (36 комментариев, если быть точным)

Автар пользователя
davidmz

Начал было патчить, но потом сломался. Статье необходима корректура — в ней адское количество лишних запятых. И «напоследок» всегда пишется слитно.

Автар пользователя
megatolya

Старайтесь совмещать jQuery и нативный JavaScript

Если человек сознательно подключил jQuery на проект, то имеет смысл по максимуму его использовать. Выигрыш от использований нативных методов вряд ли окупит хотя бы время jit'a jQuery.

Автар пользователя
mustangostang

@davidmz, упс, это вкралось при редактуре, поправили.

Автар пользователя
j0gurt

Ребят ссылочка битая на передовые практики

Автар пользователя
mustangostang

@j0gurt починили.

Автар пользователя
kuzvac

Может лучше ссылку jsperf заменить на последнюю ревизию? Там getElementById/getElementsByClassName и multiselect добавлены.

А вообще спасибо за тест, давно хотел сравнить их, да руки не доходили.

Автар пользователя
iamstarkov

@kuzvac если дашь ссылку на последнюю ревизию, то обновим

Автар пользователя
reklatsmasters

Ошибок много, вот что заметил:

1) Совмещать нативный js и jQuery - вообще плохая практика. Если это и делается, то разбить то стоит разбить код на модули. И в рамках каждого модуля придерживаться той или иной техники.

2) http://code.jQuery.com/jQuery-latest.min.js Это не CDN google, правильный их адрес https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js

3) Если не используется делегирование событий, то лучше использовать как раз click(). И не думать что там под капотом.

4) if(collection.length > 0){..} и if(collection.length){..} абсолютно разные операции, вторая ничем не лучше.

Автар пользователя
iamstarkov

1) Совмещать нативный js и jQuery - вообще плохая практика. Если это и делается, то разбить то стоит разбить код на модули. И в рамках каждого модуля придерживаться той или иной техники.

И в рамках каждого модуля придерживаться той или иной техники.

вообще не могу понять о чём ты, так как что бы ты писал на jQuery, это всегда будет написано с помощью нативного код, так как одно является подмножеством другого. И всё таки безотносительно этого, ты мог бы рассказать почему «это» плохая практика?

2) http://code.jQuery.com/jQuery-latest.min.js Это не CDN google, правильный их адрес https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js

согласен, что это не гугловый cdn, но в оригинале был именно этот адрес.

3) Если не используется делегирование событий, то лучше использовать как раз click(). И не думать что там под капотом.

Расскажи почему, пожалуйста

4) if(collection.length > 0){..} и if(collection.length){..} абсолютно разные операции, вторая ничем не лучше.

вторая короче и поэтому удобочитаемее, об этом и говорит автор, разве он не прав?

Автар пользователя
cheh

Хочу выразить благодарность - нравляться подобного формата статьи.

Автар пользователя
reklatsmasters

вообще не могу понять о чём ты

например об этом:

js $el.css({width:"12px"}) $el.get(0).style.width = "12px";

Расскажи почему, пожалуйста

1.Переносимость. 2. Короче.

вторая короче и поэтому удобочитаемее, об этом и говорит автор, разве он не прав?

Тупанул. Длина же не может быть меньше 0.Всё норм тут, согласен.

Автар пользователя
kuzvac

@matmuchrapna вот http://jsperf.com/jquery-vs-javascript-performance-comparison/30

Автар пользователя
iamstarkov

И в рамках каждого модуля придерживаться той или иной техники.

вообще не могу понять о чём ты например об этом:

$el.css({width:"12px"}) $el.get(0).style.width = "12px";

а такое бывает? Если человек пишет такое когда есть jQuery, то я уверен, это не самое страшное в его коде. А вообще я думаю, что автор имел ввиду другого рода оптимизации.

3) Если не используется делегирование событий, то лучше использовать как раз click(). И не думать что там под капотом.

Расскажи почему, пожалуйста

1.Переносимость. 2. Короче. 1. Короче.

сомнительный плюс.

1.Переносимость.

Как это влияет на переносимость?

Автар пользователя
havenchyk

Про $.data пункт из статьи верен? Нашел такой тест http://jsperf.com/jquery-data-vs-jqueryselection-data, разницы практически никакой.

Автар пользователя
havenchyk

Еще такой вопрос: "Используйте сокращенные вычисления для логических выражений" - вы такое используете? Читабельность, на мой взгляд, не повышается.

Автар пользователя
iamstarkov

@kuzvac апнул ссылку на jsperf до последней ревизии

Автар пользователя
iamstarkov

"Используйте сокращенные вычисления для логических выражений" - вы такое используете? Читабельность, на мой взгляд, не повышается.

я использую. это и правда очень удобно.

Автар пользователя
reklatsmasters

В логических операциях главное не слишком умничать. Например, такие конструкции могут только запутать:

if ( !~foo.indexOf("bar") ){ ... }

Тест на jsperf очень интересный. Почему querySelector так проседает?

UPD Кажется, ответ тут

Автар пользователя
iamstarkov

if ( !~foo.indexOf("bar") ){ ... }

во всём надо руководствоваться здравым смыслом, и в данном примере его нет

Автар пользователя
BR0kEN-
// так себе
$second.html(value);
$second
    .on('click',function(){ alert('всем привет');})
    .fadeIn('slow')
    .animate({height:'120px'},500);
// лучше
$second.html(value).on('click', function() {
  alert('всем привет');
}).fadeIn('slow').animate({height: 120}, 500);

Неудачный пример. А что еще может иметь тип radio?

$('.someclass input:radio');
Автар пользователя
iamstarkov

@BR0kEN-

Неудачный пример. А что еще может иметь тип radio? $('.someclass input:radio');

В нормальном мире ты был бы прав, только не учёл того, что type="radio" можно добавить на любой тег и sizzle должен это учитывать

Автар пользователя
BR0kEN-

@matmuchrapna, но выходит что и ты не полностью раскрываешь мысль, так как людей, добавляющих атрибуты на элементы не совместимые с ними, мало заботит качество кода и "какие-то там" оптимизации.

Автар пользователя
iamstarkov

@BR0kEN- я о том, что эти ситуацию движку селекторов тоже необходимо учитывать и это обходится автоматической подстановкой универсального селектора. Соответственно, это надо учитывать именно тем, кто думает об оптимизации

Автар пользователя
pantsarny

Чтобы использовать CDN Google, используйте в коде следующий адрес: http://code.jquery.com/jquery-latest.min.js

Разве это CDN Google?

Автар пользователя
h4

В статье много полезностей для новичков, но зачем такая категоричность и догматичность? Вот вам пара альтернативных видений.

У паттерна одного var есть как достоинства, так и недостатки. Например, нельзя просто взять и удалить последнюю из объявленных переменных (например, нажатием dd в vim):

var a = 0, b = 1, c;

CDN тоже далеко не всегда панацея. Достаточно одной неосторожной блокировки по ip.

Автар пользователя
bohdan-vorona

по поводу сокращения логических операторов... этого делать не рекомендуется, т. к. это плохо для понимания, тем более новичков. во всех книгах это пишут...

Автар пользователя
iamstarkov

@bohdan-vorona

т. к. это плохо для понимания, тем более новичков

С моей точки зрения это очень распространённая практика. А новички, на то и новички, чтобы долго понимать.

PS. А в каких книгах вы прочитали об этом?

Автар пользователя
bohdan-vorona

Стоян Стефанов "JavaScript Patterns" Маконнелл "Совершенный код" Флэнаган "Подробное руководство по JS"

Автар пользователя
froloff

Про скорость селекторов почему-то не говорят, что лучше использовать контекст, то есть не $("#container li"), а $("li", "#container"). На сферическом коне в вакууме (50 списков на 100 элементов каждый) замутил тест и результат очень впечатляет — 677 о/с против 215904 о/с :)

http://jsperf.com/context-selector-performance

Автар пользователя
rain84

Знайте особенности // bad $('#id').data(key,value); // better (faster) $.data('#id',key,value) 1. Такой способ привязки DATA-данных DOM-элементу работает, но я не нашел документации, где индентификатор DOM-элемента используется внутри $.data(). 2. Мой бенчмарк показывает обратное. Добавлять данные обычным способом получается быстрее. :)

Автар пользователя
rain84

Кое-кто выше писал что-то вроде "ой не понятно, что автор имел в виду в конструкции вроде if ( !~foo.indexOf("bar") ){ ... }".

Честно, это забавно. Ребята, у каждого языка есть свои фичи. В данном случае используется свойство возвращать "-1" в отрицательно результате работы метода "indexOf", что в битовой инверсии дает "0", т.е. "false". И ветка "if" сработает, если "bar" был не найден. Просто для того, чтобы нормально программить, надо заимейть привычку приучать себя к пониманию особенностей языка, и главное, пользоваться этими особенностями. :)

Автар пользователя
megatolya

@rain84 непонятный код не сложно писать, сложно писать понятный.

Пример

javascript var isOk = !!myVar;

VS

javascript var isOk = Boolean(myVar);

Какой вариант более читаемый? А какой использует ФичиЯзыка™?

Автар пользователя
rain84

@megatolya , хорошие вопросы. :)

Какой вариант более читаемый?

var isOk = Boolean(myVar);

А какой использует ФичиЯзыка™?

var isOk = !!myVar;

И мой встречный вопрос. Каким другим образом, кроме эмпирического, обычный смертный может понять, "понятный" код перед ним, или "непонятный"?

Автар пользователя
davidmz

Мне кажется, тут используются «фичи языка» разного уровня. !!x использует приведение типов, а !~x — битовое представление числа.

Для того чтобы писать на JS, не нужно знать про битовое представление чисел (это может понадобиться только в редчайших случаях работы с битовыми масками), тогда как приведение типов в нём встречается на каждом шагу. Соответственно, вероятность того, что «обычный» JS-программист сходу прочтёт и поймёт конструкцию !!x, гораздо выше, чем для конструкции !~x.

Автар пользователя
rain84

@davidmz, полностью согласен. Это хорошо описано во всем известном web-учебнике Ильи Кантора. На самом деле, ничего сложного в таких конструкциях нет. Просто надо заиметь привычку ими пользоваться.

Автар пользователя
megatolya

@rain84

Каким другим образом, кроме эмпирического, обычный смертный может понять, "понятный" код перед ним, или "непонятный"?

Сложно сказать, зависит от опыта человека в программировании в целом. Кому-то достаточно референса прочитать, кому-то нужно привыкать что-то использовать, а что-то нет.