Компания
512,54
рейтинг
30 июля 2013 в 11:31

Разработка → Сравнение производительности JS-библиотек


Некоторое время назад возникла задача сделать сравнительный анализ jQuery и Google Closure Library. Основным было сравнение функциональных характеристик, но помимо этого появилось желание проверить и скорости работы этих двух библиотек. Некоторые знания о внутреннем устройстве позволяли сделать предположения, но результаты тестов оказались для меня несколько неожиданными и я решил, что стоит поделиться ими с хабра-сообществом.

Организация теста


Перед тем как начать собственно сравнение пришлось сделать «тестовый движок» — несколько строк кода, которые позволяли далее запускать несколько разных тестов. После этого к сравнительному тестированию было легко добавлено также выполнение тех же операций на «голом» javascript (назовем его native-вызовами) и с использованием библиотеки ExtJS. Можно было бы добавить и еще что-нибудь, но тут запас моих знаний закончился, а изучать библиотеку только ради теста — не хотелось.
Никаких хитростей нет и подход к тестированию самый примитивный. Собственно замер обеспечивался крохотной функцией, которая просто выполняла требуемую функцию необходимое число раз и возвращала скорость исполнения — количество операций в миллисекунду:
runTest = function(test, count){
  var start = new Date().getTime();
  for(var i=1;i<count;i++) test();
  var end = new Date().getTime();
  var time = end - start;
  return count/time;
}

Для того, чтобы запускать несколько однотипных тестов используя разные библиотеки была добавлена функция, принимающая на вход целую группу тестов:
runGroup = function(label, tests, count){
  var res = {};
  for(var key in tests) res[key] = runTest(tests[key], count);
  saveResult(label, res);
}

Это позволило сделать вызов теста в таком «наглядном» виде:
runGroup('Имя теста',{
"native": function1,
"jQuery": function2,
"closure": function3,
"extJS": function4
})

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

Тестируемые операции


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

Поиск элемента по идентификатору


Без поиска элементов не обходится, наверное, ни одна web-страница. Все знают, что поиск по id наиболее оптимален, и используют его. Для теста использовался следующих код:
document.getElementById('id');  // native
goog.dom.getElement('id');      // closure
$('#id');                       // jQuery
Ext.get('id');                  // ExtJS


Поиск элементов по классу


Естественно, поиском по идентификатору дело не ограничивается. Зачастую приходится искать элементы более «изощренным» образом. Для теста я выбрал поиск по классу:
document.getElementsByClassName('class'); // native
goog.dom.getElementByClass('class');      // closure
$('.class');                              // jQuery
Ext.select('.class');                     // ExtJS


Добавление элемента


Естественно, надо уметь добавлять элементы на страницу. Для тестовых целей использовалось добавление однотипных span непосредственно к body. Тут код без использования библиотек уже существенно длиннее, чем с ними:
goog.dom.appendChild(document.body, goog.dom.createDom('span',{class:'testspan'})); // closure
$(document.body).append($('<span class="testspan">'));                              // jQuery
Ext.DomHelper.append(document.body, {tag : 'span', cls : 'testspan'});              // ExtJS
// native
var spn = document.createElement('span');
spn.setAttribute('class','testspan');
document.body.appendChild(spn);


Определение класса элемента


Естественно, зачастую возникает и потребность в определении свойств элементов. Я выбрал определение списка классов, присвоенных элементу (поиск самого элемента осуществлялся вне цикла тестирования):
nElement.getAttribute('class').split(' ');  // native
goog.dom.classes.get(gElement);             // closure
jElement.attr('class').split(' ');          // jQuery
eElement.getAttribute('class').split(' ');  // ExtJS


Изменение класса элемента


Обычно определять класс даже и не нужно — необходимо добавить его, или удалить. Все библиотеки предлагают естественный метод toggle для данного случая, но вот на голом javascript пришлось написать целую портянку:
goog.dom.classes.toggle(gElement, 'testToggle');  // closure
jElement.toggleClass('testToggle');               // jQuery
var classes = eElement.toggleCls('testToggle');   // ExtJS
// native
var classes = nElement.className.split(' ');
var ind = classes.indexOf('testToggle');
if(ind==-1) classes.push('testToggle');
else classes.splice(ind,1);
nElement.className = classes.join(" ");


Изменение стиля элемента


Ну и наиболее часто используемая операция с элементом — установка ему определенных css-свойств:
nElement.style.backgroundColor = '#aaa';                      // native
goog.style.setStyle(gElement, {'background-color': '#aaa'});  // closure
jElement.css({'background-color': '#aaa'});                   // jQuery
eElement.setStyle('backgroundColor','#aaa');                  // ExtJS


Собрав воедино все описанные выше элементы я получил страничку для тестирования, полный текст которой можно увидеть под спойлером. Библиотеки использовались из соответствующих CDN (версия 1.10.2 для jQuery, 4.2.0 для ExtJs и trunk-версия для closure).Любой желающий может сохранить это в html-файл и повторить тест или добавить туда что-то свое.
Длинный HTML
<!DOCTYPE html>
<html>
 <head>
  <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
  <script src='http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js'></script>
  <script src="http://cdn.sencha.com/ext/gpl/4.2.0/ext-all.js"></script>
  <script>
    goog.require('goog.dom');
    goog.require('goog.dom.classes');
    goog.require('goog.style');
  </script>
  <style>
    table{border-collapse:collapse;}
    th {font-size:120%; }
    td {border: solid black 1px; width: 180px; height: 60px; text-align: center; }
    .rowlabel {width: 120px; text-align: left; background-color: beige;}
    .avg {font-weight: bold; font-size:120%; color: darkblue;}
  </style>
  <title>Benchmark</title>
 </head>
 <body>
  <div id="testid" class="testclass"></div>
  <button onclick="getBenchmark()">Run</button>
  <table id="result"></table>
 </body>
</html>
<script>
var runCount = 4;       // сколько раз запускать весь набор тестов
var testSize = 1000;     // количество итераций в одном запуске

// поехали...
getBenchmark = function(){
  for(var i = 0;i<runCount;i++) allTests();
  showResults();
}

allTests = function(){
  // сохраняем ссылку на элемент для последующих манипуляций
  var nElement = document.getElementById('testid');
  var gElement = goog.dom.getElement('testid');
  var jElement = jQuery('#testid');
  var eElement = Ext.get('testid');

  // поиск по идентификатору
  runGroup('Id lookup',{
    "native":  function(){var element = document.getElementById('testid');},
    "closure": function(){var element = goog.dom.getElement('testid');},
    "jQuery":  function(){var element = jQuery('#testid');},
    "ExtJS":   function(){var element = Ext.get('testid');}
  }, 500*testSize);
  // поиск по классу
  runGroup('Class lookup',{
    "native":  function(){var elements = document.getElementsByClassName('testclass');},
    "closure": function(){var elements = goog.dom.getElementByClass('testclass');},
    "jQuery":  function(){var elements = jQuery('.testclass');},
    "ExtJS":   function(){var elements = Ext.select('.testclass');}
  }, 200*testSize);
  // добавление элемента
  runGroup('Append span',{
    "jQuery":  function(){jQuery(document.body).append(jQuery('<span class="testspan">'));},
    "closure": function(){goog.dom.appendChild(document.body, goog.dom.createDom('span',{class:'testspan'}));},
    "ExtJS":   function(){Ext.DomHelper.append(document.body, {tag : 'span', cls : 'testspan'});},
    "native":  function(){
                  var spn = document.createElement('span');
                  spn.setAttribute('class','testspan');
                  document.body.appendChild(spn);
                }
  }, testSize);
  // удалим все добавленные элементы
  jQuery('.testspan').remove();
  // определение класса элемента
  runGroup('Read classes',{
    "native":  function(){var classes = nElement.getAttribute('class').split(' ');},
    "closure": function(){var classes = goog.dom.classes.get(gElement);},
    "jQuery":  function(){var classes = jElement.attr('class').split(' ');},
    "ExtJS":   function(){var classes = eElement.getAttribute('class').split(' ');}
  }, 100*testSize);
  // изменение класса элемента
  runGroup('Toggle class',{
    "closure": function(){goog.dom.classes.toggle(gElement, 'testToggle');},
    "jQuery":  function(){jElement.toggleClass('testToggle');},
    "ExtJS":   function(){var classes = eElement.toggleCls('testToggle');},
    "native":  function(){
                  var classes = nElement.className.split(' ');
                  var ind = classes.indexOf('testToggle');
                  if(ind==-1) classes.push('testToggle');
                  else classes.splice(ind,1);
                  nElement.className = classes.join(" ");
                }
  }, 50*testSize);
  // изменение css-свойства
  runGroup('Styling',{
    "native":  function(){nElement.style.backgroundColor = '#aaa';},
    "closure": function(){goog.style.setStyle(gElement, {'background-color': '#aaa'});},
    "jQuery":  function(){jElement.css({'background-color': '#aaa'});},
    "ExtJS":   function(){eElement.setStyle('backgroundColor','#aaa');}
  }, 50*testSize);
}


var savedResults = {};
var tests = [];

// форматирование результатов
showResults = function(){
  jQuery('#result').empty();
  // имена тестов - в заголовки столбцов
  var str = '<tr><th></th>'
  for(var i=0;i<tests.length;i++){
    str += '<th>' + tests[i] + '</th>';
  }
  str += '</tr>';
  for(var label in savedResults){
    // отдельная строка для каждой группы
    str += '<tr><td class="rowlabel">'+label+'</td>'
    for(var i=0;i<tests.length;i++){
      str += '<td>';
      var key = tests[i];
      var res = savedResults[label][key];
      if(res){
        var detail = '';
        var total = 0;
        for(var k=0;k<res.length;k++){
          if(k==0) detail += Math.round(res[k]);
          else detail += ', ' + Math.round(res[k]);
          total += res[k];
        }
        if(res.length > 0) total = total / res.length;
        str += '<span class="avg">'+Math.round(total)+'</span><br>'+detail;
      }
      str+='</td>';
    }
  }
  jQuery('#result').append(str);
}

// сохранение результатов
saveResult = function(label, result){
  if(!savedResults[label]) savedResults[label] ={};
  for(var key in result){
    if(tests.indexOf(key)==-1) tests.push(key);
    if(!savedResults[label][key]) savedResults[label][key] = [];
    savedResults[label][key].push(result[key]);
  }
}

// запуск группы однотипных тестов
runGroup = function(label, tests, count){
  var res = {};
  for(var key in tests) res[key] = runTest(tests[key], count);
  saveResult(label, res);
}

// выполоняем функцию требуемое число раз
runTest = function(test, count){
  var start = new Date().getTime();
  for(var i=1;i<count;i++) test();
  var end = new Date().getTime();
  var time = end - start;
  return count/time;
}
</script>



Результаты теста


Указанный тест я выполнил во всех браузерах, установленных на моей машине. Это было сделано не с целью сравнить браузеры, а для того, чтобы убедиться, что библиотеки под разными браузерами ведут себя относительно одинаково. Соответственно, заботиться об обновлении версий я тоже не стал. Результаты (число операций в миллисекунду) в табличках ниже. (Жирным шрифтом в каждой ячейке показано усредненное значение четырех тестов, обычным -значения в каждом из тестов).

Chrome


Версия 28.0.1500.72


Opera


Версия 12.10.1652


Firefox


Версия 22.0


Internet Explorer


Версия 9.0.8112.16421


Итоги


Наглядно сравнительные результаты можно увидеть на диаграмме, которая построена по результатами тестирования в Chrome (результаты были нормированы, так чтобы разные группы тестов уместились на одной диаграмме). Чем длинее полоска на графике, тем быстрее:


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

Не думаю, что из этих результатов стоит делать далеко идущие выводы — для подавляющего большинства web-приложений не требуется действительно массового выполнения ни одной из указанных операций, но иногда все-таки стоит обращать внимание на используемые инструменты и выбирать те, которые наилучшим образом подходят для задачи. Ни одна из библиотек не запрещает использование native-методов работы с DOM и при необходимости всегда можно обратиться к ним минуя все библиотечные обертки.
Автор: @bay73

Комментарии (92)

  • +5
    В нативном JS есть же более универсальные функции querySelectorAll и querySelector и довольно много кто поддерживает caniuse.com/queryselector.
  • +20
    jQuery — хоть и самая медленная, но, имхо, самая удобная и продуманная библиотека. И в большинстве скорость разработки окупает скорость работы.
    Вот бы в браузерах оптимизации для jQuery (учитывая повсеместное использование), но, может, я слишком многого прошу…
    • +3
      По поводу медленности ее всегда можно оптимизировать, она с недавних пор стала модульной + убраны поддержки old browsers.
      • +1
        К стати да, не понятно, какая это версия. что-то мне кажется, 1.9.х а не 2.х
      • +5
        Проверил на 2.0.3 — отличий от 1.10.2 в тесте нет (Chrome 30)
    • 0
      Я готов поспорить. В d3js (который в принципе для другого предназначен, но манипуляции с DOM там тоже есть) некоорые вещи сделаны более приятно, например:

      d3.select('body')
        .append('div')
          .append('span')
            .attr('class', 'foo')
      
    • 0
      Я бы поспорил. Мне и на чистом JS достаточно удобно писать. Особенно временами поспорил бы по поводу окупаемости скорости.
      Судя по тестам (а я и сам уже давно такое подозревал) jQuery стал(!) не просто медленным, а дико тормознутым. Особенно drag&drop и в целом. Дико раздражает, когда окно при перетаскивании движется за мышкой с конечной предельной скоростью и с заметной задержкой.
  • +6
    А версии библиотек можно увидеть?
    • 0
      Версии можно увидеть в тексте HTML (там явная загрузка из CDN). Но добавил упоминание версий непосредственно в текст.
    • +1
      В head можно посмотреть. Их автор из cdn в основном подгружает.
  • +3
    Вот смотрю на цифры и первое впечатление это время в мс за 1000 итераций. Но если так, то JQuery самая быстрая.
    Что означают эти цифры? Хочется более объективного сравнения.

    А вообще для чего используются библиотеки? Для манипуляции с DOM и выполнения GET и POST запросов к серверу. Еще разбор JSON и XML. Хотелось бы рассмотреть и эти моменты и уже решить какую библиотеку лучше выбрать для проектов, где критична скорость.
    Буду очень признателен.
    • 0
      Спасибо за замечание — добавил указание, того, что это число операций в миллисекунду.
    • 0
      Время выполнения GET И POST таким образом вряд ли стоит замерять — все-таки там время отклика сервера будет превалюрующим, а вот разбор строк можно померять.
      • 0
        На локальный сервер отправлять, который пустую статическую страницу отдает?
  • 0
    Не хватает jQuery версии 2.x — она должна быть побыстрее.
  • +12
    Результаты jQuery можно немного улучшить:

    'Append span':
    вместо jQuery(document.body).append(jQuery('<span class="testspan">'));
    записать jQuery(document.body).append('<span class="testspan">');
    


    приблизительно скорость увеличивается на 20% в Хроме (в других не проверял), но это всё равно мало.

    'Styling':
    вместо jElement.css({'background-color': '#aaa'});
    записать jElement.css('background-color', '#aaa');
    


    + 40% относительно исходного результата…

    ps. порадовало, что extjs по некотором тестам немного native превосходит в некоторых браузерах))) надо как-нить заглянуть в его «кишки»
    • +1
      Раньше я особо не задумывался о способе написания вызовов в jQuery, да и окружающие, видимо, тоже (судя по исходникам) — тот же css выствляют поочередно каждым из способов. :)

      Меня тоже результаты ExtJS порадовали. Но я это списываю на свои кривые руки при вызове native
      методов.
      • +4
        Для 'Read classes':

        var classes = nElement.className.split(' ');
        


        А для 'Toggle class':

        var classes = " " + nElement.className + " ";
        if (classes.indexOf(" " + 'testToggle' + " ") == -1) {
          nElement.className += " " + 'testToggle';
        } else {
          nElement.className = classes.replace(" " + 'testToggle' + " ", " ").replace(/^ /, "").replace(/ $/, "");
        }
        


        вот теперь extjs отстает от native)
        у вас, по сути, отставание на 10-20%, причем в операциях в миллисекунду, т.е. пользователь этого точно не заметит, даже при количестве в 1000 операций на одно его действие, что является редкостью.
        • 0
          Точно — в toggle class я className использовал, а вот в read classes почему-то о нем забыл. Спасибо. Разница небольшая, но как раз объясняет скорость ExtJS.
    • +2
      Это значит только что native код был плохо оптимизирован.
    • 0
      Мне тоже показалось странным, что extjs быстрее native. Это как такое возможно? Всегда думал, что библиотеки сводят свой код к native.
      • 0
        Не в данном случае, а вообще, такое возможно — библиотека, в отличии от native может кешировать некоторые данные.
        • 0
          ExtJS достаточно сильно кэширует данные. Их постоянно засыпают багами по производительности и они постоянно в эту сторону оптимизируют библиотеку.
  • +2
    Пожалуйста, указывайте рядом с таблицами и/или диаграммами в каких попугаях данные представлены. Не у всех есть время вчитываться в каждое словосочетание в поисках «количества операций в миллисекунду».
    ПС: поздно увидел аналогичное замечание выше.
  • 0
    Может лучше заюзать SlickSpeed для тестирование выборок по DOM?
    Ну и ещё замечания:
    — Как часто вам приходится получать все классы элемента? Я вот не припомню такого…
    — Добавление элементов в дом полагаясь на $("<....>") — ну очень спорный момент, я даже используя jQuery обычно пишу document.createElement() — это не только быстрее, но и зачастую удобней.
    — При применении CSS стиля в jQuery используете хэш, но зачем? В итоге же получаем дополнительный перебор свойств…

    P.S. Я не ставил перед собой задачу защищать jQuery, просто тест какой-то за уши притянутым мне кажется, такой вот, пользовательский, ну и подборка очень узкая, как будто вы решили потестить только те фреймворки, по котором холиварите между собой.
    • +1
      Я бы как раз хотел защитить jQuery. :)
      Ну а код использовался аналогичному в реальных проектах. Классы элемента получать действительно не очень нужно — об этом и в статье написано. А вот добавление с использованием $('<...>') вижу в каждом втором файле.
      • 0
        О_о
        Похоже нет на ваших девов мастер-классов от Ильи Кантора, у него есть отличная тема по оптимизации… ;)
  • +5
    А можно добавить zeptojs.com/? Он имеет совместимость с jQuery API, интересно сравнить.
    • 0
      Поддерживаю. Статье не хватает Zepto.
    • 0
      Перешел по вашей ссылке. Вдохновился описанием — добавил zepto в тест. Ну а результаты сильно разочаровали
      chrome
      chrome
      Он везде оказался медленней чем jQuery (ну кроме «чтения классов»), плюс нет поддержки IE, скрипач не нужен.
  • 0
    Хотелось бы увидеть вот эту библиотеку в таблице: yass.webo.in
    • 0
      Сразу отвечаю и akira и bolk — зная где взять библиотеку, и как с ее использованием описанные выше операции выглядят — можно легко расширить тест. Займусь этим на досуге. Ну а если любители библиотек пришлют мне вызовы для выполнения операций, аналогичных тем, что в статье, то дело будет быстрее.
      • –1
        Хочется еще операций, которые очень часто используются: POST, GET запрос и парсинг JSON
        • +1
          Выше я уже писал — POST, GET слишком сильно от сети и сервера зависят и влияние библиотек померить трудно будет. А вот парсин json попробую к следующему разу добавить.
          • –1
            Хотя запросы не часто выполняются и скорость этого не в приоритете.

            Еще при желании можно поиск в массивах глянуть и их сортировку. Просто помню на JQuery очень тормозило переписывал все в ручную. Объемы там были огромные.
        • +4
          Во вменяемых браузерах парсинг JSON нативен.
          Ну а если библиотека не использует нативный парсинг при его наличии, то это плохая библиотека.
    • 0
      да ладно тебе :) Даже имея библиотеку, которая быстрее closure, это ничего не дает — все равно все jQuery используют
      • 0
        А тебе не надо было останавливаться :) Сделал бы её совместимой по вызовам с jQuery, люди бы легко её заменяли :)
  • +1
    Оставлю тут, может кому пригодится: Ускорение работы с DOM
  • +3
    Интересно, если собрать jQuery с родным querySelectorAll вместо Sizzle (в jQuery 2.0 это возможно) — как изменятся результаты теста?
    • +1
      Sizzle всегда пытается вызвать querySelectorAll обернув его в try/catch и только в том случае если браузер упал при попытке это разобрать, запускает свой внутренний поиск.

      А еще перед этим у него есть отдельные проверки если селектор является просто ID — запускается getElementById(), если просто класс — getElemenstByClassName() — это как раз случаи из статьи.

      Т.е. здесь собственного поиска Sizzle не изменрялась. Измерялось время на обертки, регексп чтобы понять является ли селектор просто ид/классом/тегом и собственно вызов getElementById/getElemenstByClassName.

      Поэтому становится еще непонятнее почему так медленно?
      • –1
        Не всё так просто:
        The Sizzle selector engine. When this module is excluded, it is replaced by a rudimentary selector engine based on the browser's querySelectorAll method that does not support jQuery selector extensions or enhanced semantics.

  • +2
    Было дело, тоже интересовался сравнительно недавно. На самом деле, счет идет на такое нереальное количество операций в секунду, что на практике там совершенно неважно, что — jQuery или native — пользователь не видит разницы ни в одном браузере (особенно, если простые скрипты, коих большинство).

    Тем не менее! Всегда было интересно — почему результаты того же jQuery НАСТОЛЬКО отличаются? Почему они для селекторов не используют native-варианты, если они присутствуют? Или, если используют, то почему такой оверхед. Странно, учитывая популярность jQuery.
    • +1
      Да, в подавляющем большинстве приложений разницы никто не заметит. Но бывают случаи — например, красивый грид с данными на страничке — если каждую ячейку отдельным $.appendChild отрисовывать, то тормозить будет уже заметно.
      • 0
        Это да, поэтому для таких тяжеловесных виджетов приходится реализовывать многие вещи другими средствами, даже если в целом в проекте jQuery используется.
  • +1
    А можете добавить в тест Dojo? dojotoolkit.org/
  • +3
    Ну и V8, конечно, просто монстр :)
  • +6
    Не знаю, как там в библиотеках, но нативный код можно написать от задачи и результат будет разный, к примеру:

    document.getElementById('id');
    
    vs
    document.querySelector('#id');
    

    document.getElementsByClassName('class');
    
    vs
    document.querySelectorAll('.class');
    

    var spn = document.createElement('span');
    spn.setAttribute('class','testspan');
    document.body.appendChild(spn);
    
    vs
    document.body.innerHTML='<span class="testspan"></span>';
    
    vs
    document.body.insertAdjacentHTML('afterbegin'/*beforeend*/,'<span class="testspan"></span>');
    

    nElement.getAttribute('class').split(' ');// Что это???
    
    vs
    nElement.classList;
    

    var classes = nElement.className.split(' ');// ...
    var ind = classes.indexOf('testToggle');
    if(ind==-1) classes.push('testToggle');
    else classes.splice(ind,1);
    nElement.className = classes.join(" ");
    
    vs
    nElement.classList.toggle('testToggle');
    

    И так далее…

    Так что нужно еще учитывать и то, что в итоге нужно получить.
  • 0
    интересно было бы посмотреть еще в этом перечне производительность такой библиотеки как d3.js
  • +4
    В jQuery медленно работает функция $(), которая создает обьект с кучей вспомогательных методов. С этим обьектом очень удобно работать выстраивая цепочки, но при этом приходится жертвовать скоростью.

    Обычная практика оптимизации jQuery — это минимизация вызовов $() и сохранения результата в переменные.

    Зная эту особенность можно свободно использовать эту либу в больших проектах.
    • НЛО прилетело и опубликовало эту надпись здесь
      • +4
        Вы просто по другому назвали функцию, медленно работает именно вызов.

        $('.sel').mousemove(function() {
          $('.sel2').css({...});
        });
        

        Этот кусок кода будет работать значительно медленнее, чем вот такой:

        var $sel2 = $('.sel2');
        $('.sel').mousemove(function() {
          $sel2.css({...});
        });
        


        То есть объект jQuery создается медленно, после чего с ним легко и быстро работать.
        • 0
          Но второй вариант — не просто оптимизация первого, там может быть другая логика. Элемента ".sel2" может еще не существовать при объявлении callback-а на событие. И не всегда есть возможность отследить его появление. Придется делать проверку внутри обработчика события. Ну, впрочем, понятное дело — жертвуем простотой ради производительности.
          • +1
            Всё правильно:) Я просто накидал пример, думал так будет понятнее.
        • +2
          Ну правильно, каждый раз, когда вы печатаете символ $, то jQuery начинает бегать по всему dom в поисках элемента, а вот если таких будет тысячи?) Всегда надо стараться кешировать результат в переменную.
          • 0
            Выше уже объявнили, что jQuery старается использовать нативные функции браузера. Медленно работает именно создание объекта который возвращаться из $().
      • 0
        А это то чем поможет? Просто алиас функции. Он также создаст обертку вокруг объекта элемента, которую в документации они называют «jQuery object».
  • +6
    так ну я не стал ждать с моря погоды (пока потестят мою любимую библиотеку d3.js) и сам провел тест правда только в Chrome Версия 30.0.1573.2 dev-m.
    И так:
    Поиск элемента по идентификатору
    "native":  function(){var element = document.getElementById('testid');},
    "closure": function(){var element = goog.dom.getElement('testid');},
    "jQuery":  function(){var element = jQuery('#testid');},
    "ExtJS":   function(){var element = Ext.get('testid');},
    "d3js": function(){var element = d3.select("#testid");}
    

    Поиск элементов по классу
    "native":  function(){var elements = document.getElementsByClassName('testclass');},
    "closure": function(){var elements = goog.dom.getElementByClass('testclass');},
    "jQuery":  function(){var elements = jQuery('.testclass');},
    "ExtJS":   function(){var elements = Ext.select('.testclass');},
    "d3js": function(){var elements = d3.select('.testclass');}
    

    Добавление элемента
    "jQuery":  function(){jQuery(document.body).append(jQuery('<span class="testspan">'));},
    "closure": function(){goog.dom.appendChild(document.body, goog.dom.createDom('span',{class:'testspan'}));},
    "ExtJS":   function(){Ext.DomHelper.append(document.body, {tag : 'span', cls : 'testspan'});},
    "d3js": function() {d3.select(document.body).append("span").attr("class", "testspan");},
    //native
    

    Определение класса элемента
    "native":  function(){var classes = nElement.getAttribute('class').split(' ');},
    "closure": function(){var classes = goog.dom.classes.get(gElement);},
    "jQuery":  function(){var classes = jElement.attr('class').split(' ');},
    "ExtJS":   function(){var classes = eElement.getAttribute('class').split(' ');},
    "d3js": function(){var classes = d3Element.attr("class").split(' ');}
    

    Изменение класса элемента
    собственно метод сlassed без второго параметра нужен для определения наличия класса у объекта
    "closure": function(){goog.dom.classes.toggle(gElement, 'testToggle');},
    "jQuery":  function(){jElement.toggleClass('testToggle');},
    "ExtJS":   function(){var classes = eElement.toggleCls('testToggle');},
    "d3js": function() {d3Element.classed('testToggle', !d3Element.classed('testToggle'));},
    //native
    

    Изменение стиля элемента
    "native":  function(){nElement.style.backgroundColor = '#aaa';},
    "closure": function(){goog.style.setStyle(gElement, {'background-color': '#aaa'});},
    "jQuery":  function(){jElement.css({'background-color': '#aaa'});},
    "ExtJS":   function(){eElement.setStyle('backgroundColor','#aaa');},
    "d3js": function(){ d3Element.style('background-color', '#aaa'); }
    


    image
    • 0
      • 0
        Uncaught ReferenceError: goog is not defined

        Видимо из-за того, что Dropbox по https отдаёт, а хром после этого не разрешает использовать не-https js-файлы:

        [blocked] The page at https://dl.dropboxusercontent.com/spa/6x4vg7uwuzglgh3/testJsF/public/index.html ran insecure content from http://code.jquery.com/jquery-1.10.2.min.js.
        • 0
          ну так на значок щита в адресной строке нажми и все будет работать!
          • 0
            Да, уже заметил.

            А давно хром такое начал?
            Раньше, помнится, просто автоматом зачёркивал значок замка, вроде как.
            • 0
              Я не знаю просто когда стал пользоваться dropbox apps то наткнулся на это.
    • 0
      Приятней смотреть на график, если не затруднит.
      • 0
        можно подумать об этом и сделать их на d3.js =)
  • +1
    Слышал что выборка происходит справа налево, в связи с этим вопрос: какой способ будет быстрее:

    $('.wrapper1 a').blabla();
    или
    $('.wrapper1').find('a').blabla();

    Предполагается что есть много разных <div class='wrapper'>... </div> и в каждом много <a.....>
    • +1
      В современных браузерах селекторы будут обрататываться браузерным .querySelectorAll, а значит второй будет медленнее (ибо 2 вызова querySelectorAll + js-ный бутерброд между ними).

      Кроме того разница будет заметна еще больше в старых ИЕ (6-8). Если вы хорошо относитесь к пользователям ИЕ 6-8, то да: стоит избегать ситуации когда самый правый элемент селектора состоит только из класса (в старых ИЕ из-за отсутсвия getElementsByClassName() это приведет к полному перебору ВСЕХ элементов, и проверке имеет ли элемент класс), и добавлять к самому правому элементу селектора хотя бы имя тега.

      А вот второй ваш способ есть смысл применять только в случае когда в вашем селекторе есть нечто что 100% не может быть обработанно querySelectorAll. Обычно это Sizzle-овый селекторовый сахар, типа псевдоклассов :eq() :not() :contains(), :first, :last, :even, :odd, :gt, :lt, :eq (они перечислены на главной странице Sizzle). Тогда ту часть которая может быть распарсена querySelectorAll стоит запускать сначала через $(), а остальное через .find()
      • 0
        IE7 уже умер, а в IE8 уже есть querySelectorAll(). Но насчёт того, что он будет быстрее, не уверен. Оптимизировали ли движки его работу для таких запросов?
        • 0
          Вы правы отчасти. Вообще, с ИЕ8 все интересно: с одной стороны в нем уже есть querySelectorAll, но с другой стороны в нем все еще нет getElementsByClassName.

          Поэтому если селектор простой (т.е. содержит только ид/классы/теги/неймы и отношения " " и >) то да, querySelectorAll справится и все будет гуд.

          А вот если в селекторе будет что угодно кроме этого, то запустится Sizzle-овый внутренний поиск, который уже не пытается использовать querySelectorAll, а использует только getElementById, getElementsByClassName, и т.д. И поскольку getElementsByClassName в ИЕ8 нет, по факту довольно безобидный с виду запрос

          $('.second-choice:checked')

          превратится в

          $('*').filter('.second-choice:checked');

          а это нереально медленно, ибо перебирает совсем все элементы, начиная от html/head/body/script и заканчивая последней a/b/i/sup. И в этом случае простое добавление тега input в селектор ускорит его в ИЕ8 в сотни раз.
  • +1
    Благодарю за удовольствие от чтения статьи.
    Тесты немного синтетичные. Ведь в родном подходе closure-library используется лишь половина вышеуказанного. Первое и второе не используется вообще. Последнее используется очень ограниченно.

    Перед созданием клиента html-5 игрушки проводил массу подобных бенчмарков и сравнений (приводить не буду изза-специфики). В результате обнаружил, что отзывчивость и субъективное восприятие скорости в реализации на closure-library, extjs, zepto, jquery отличалось на порядки. При этом на много большее влияние на общую отзывчивость имела архитектура библиотеки компонент. Так к примеру sencha контроллы работали на одном уровне с closure, а jquery плагины прилично отставали. Когда-же стравнил реализацию анимаций, то closure отстала от jquery (что легко вылечилось заменой одной функции).

    Я был-бы рад увидеть сранение более высокоуровневых вещей.
    • +1
      Я старался сравнивать самые простые операции, умещающиеся в одну строчку (да и то, судя по комментам, далеко не везде наилучшим образом написал). Для более сложных вещей найдется уже несколько вариантов реализации на каждой из библиотек. И производительность от выбора вариавнта будет зависеть больше, чем от выбора библиотеки.
      Хотя можно было бы организовать своеобразный тест TPC — коллективным разумом выбрать наиболее характерные операции и сравнить их реализации.
  • 0
    Сравнивать надо возможности, остальное вторично.
    Например:
    1) Кто из этих фреймворков поддерживает псевдоселектор ":header"? Или что-то типа этого.
    2) Кто и на каком уровне поддерживает расширение?
    3) Кто и где упадет при разборе гигантских данных? Например, переполнит стек вызовов.
    И так далее.

    Пока вы только создали ситуацию, когда джуниор придет и скажет «я буду везде писать $('#id'), а не сохранять его в переменную, потому что наш фреймворк писец как для этого оптимизирован».

    Можно все написать на нативном яваскрипте и оптимизировать все, например под V8.
    Или использовать фреймворк с перегрузкой аргументов.
    Или строго типизированный фреймворк с неймспейсами.
    Или модульный.
    Или…

    Вот из-за этих «или» и надо сравнивать возможности. Потому что скорость, вопрос относительный. Усатый мужик с языком еще доказал.
    • 0
      Возможности это хорошо. Но сколько возможностей неюзабельны по причинам производительности.
      • 0
        Например?
        • 0
          О, примеров огромное количество.
          Последний с которым сталкивался — плагин с inline редактированием таблици. Красиво, просто, функионально, удобно. Но слишком медленно (на i7). Думаю у всех есть достаточно подобного опыта.
          • 0
            Я правильно вас понял: криворукий плагин одного из фреймворков бросил в ваших глазах тень на производительность ведущих фреймворков, которые пишут лучшие программисты со всего мира?
            • +2
              Пример очень равнорукий, красивый и качественно написаный ;). Просто в нём в жертву функциональности итп была потеряна производительность.
              Это лишь пример к этому посту.

              P.S.
              А по поводу
              ведущих фреймворков, которые пишут лучшие программисты

              Не впадайте в идолопоклонничество ;)
              Лучший меч галактики не сравнится с дешевым скальпелем в избавлении от аппендицита. Нет совершенства в программировании, всегда чем-то жертвуют и лучшие программисты. По этому при выборе инструментальных средств для конкретной задачи нужно использовать обьективные критерии а не авторитет лучших собаководов.
              • –2
                Я не впадаю в поклонничество или что-то там, вы просто подумайте — вы лично сколько сделали фреймворков? И сколько людей ими пользуются?.. Вы даже не вступили с эти людьми в интелектуальную гонку, а уже считаете их людьми не первого сорта.

                По поводу криворукости кода, то надо просто понимать, что есть стек в языке и что надо его грамотно заполнять, что в старом эксплорере, что в последнем хроме. Например, с помощью Raphael можно нарисовать в старом браузере сотни тысяч точек на графике и не заблокировать пользовательский интерфейс. А можно сделать простой плагин для jQuery и повалить браузер.
                • +5
                  вы лично сколько сделали фреймворков?
                  Открытых ни одного. На за 20+ лет наследил прилично (скорее всего Вы тоже пользуетесь чем-то к чему приложил голову).

                  Вы даже не вступили с эти людьми в интелектуальную гонку,

                  Я вообще в гонках не участвую. Предпочитаю просто делать работу и иногда слать патчи.
                  а уже считаете их людьми не первого сорта.

                  С чего вы это взяли? Я не людей на сорты делю. Есть фраза «не боги горшки обжигают».

                  По поводу криворукости… и повалить браузер.

                  Разговор ни о чем. В чем Ваш посыл? Какая криворукость в отличном плагине? Да тормозит с большими простынями. Так он предназначен для мелких. Баланс не в нужную кому-то сторону не критерий криворукости, а лишь еще один момент в пользу того что выражение
                  Сравнивать надо возможности, остальное вторично.
                  не всегда верно. И данная статья есть полезна для оценки.

                  Дальше отвечать не буду. Ибо этот разговор — немого с глухим.
  • 0
    Ну так с этого я статью и начал — сравнивались функциональные возможности, а вопрос быстродействия был второстепенным. Но эту статью я посвятил именно производительности.
  • 0
    Почему для тестов не использовали проверенный годами benchmark.js (в основе jsperf.com)?

    Во-вторых, использовать методы getElements{ByTagName, ByClassName /*, etc */} можно просто ради интереса (зная что они возвращают «live» NodeList/HTMLCollection, которые малопригодны для чего-то сложнее простых операций выборки/перебора и своих велосипедов). Но тогда где querySelector{All}?

    На данный момент, большинство либ и селекторных движков по типу jQuery работают по одинаковому принципу ± свои оптимизации. Если мы видим значительную разницу в скорости (порядки), значит используются свои обёртки, другие подходы (например просто ф-я, которая парсит селектор, выбирает и возвращает тот же live NodeList/HTMLCollection) или недопустимые хаки (модификация оригинального DOM), но тогда мы теряем в удобстве и скорости разработки, которую даёт jQuery, что гораздо важнее.
    • 0
      Попробую ответить
      Почему для тестов не использовали проверенный годами benchmark.js (в основе jsperf.com)?
      Потому-что мне было проще написать несколько строчек самому и получить готовую отформатированную табличку с результатами.

      Во-вторых, использовать методы getElements{ByTagName, ByClassName /*, etc */} можно просто ради интереса (зная что они возвращают «live» NodeList/HTMLCollection, которые малопригодны для чего-то сложнее простых операций выборки/перебора и своих велосипедов). Но тогда где querySelector{All}?
      Как я уже писал — для тестов использовались вызовы, которые широко встречаются в имеющемся коде. jQuery селектор вида $('.class') я встречаю очень часто. Естественно пришлось сделать его аналоги (конечно же не полный эквивалент) и для других библиотек.
  • 0
    возникает вопрос, как так получилось, что в тесте Read Classes ExtJs опережает native? Там есть кэш?
    • 0
      Тут дело не в кэше, а в моих кривых руках — для native я вместо element.className написал element.getAttribute('class').
  • 0
    Для начала сделать нужно так
    Поиск элемента по идентификатору
    "jQuery":  function(){var element = jQuery('#testid')[0];},

    Поиск элементов по классу
    "jQuery":  function(){var elements = jQuery('.testclass')[0];},
  • 0
    Интересно, спасибо за информацию.
    ИМХО, важный момент касаемо Google Closure, то, что в первую очередь это компилятор, так что имеет смысл тесты компилировать, желательно с использованием advanced compilation.
    • 0
      Вы переоцениваете этот компилятор. На таких простых вызовах, как в тесте компилатор closure не даст выигрыша в производительности. В данном случае на выходе компилятора будет ровно такой же javascript код, как и на входе.
  • +4
    Как один из разработчиков «медленной» библиотеки позволю себе прокомментировать результаты:

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

    Многие тесты как jQuery так и остальных библиотек написаны довольно спорно, это видно как и по результатам: ванильный джаваскрипт не может быть медленнее библиотечных аналогов; так и по самим тестам: как пример тесты селекторов нужно выполнять в отдельном фрейме, для того что бы обеспечить изолированность выполнения каждого теста, например как сделано у нас – github.com/jquery/sizzle/blob/master/speed/

    Скорость имеет значение, но мы так же обязаны балансировать между размером гзипованного кода и стабильностью АПИ библиотеки, как пример: мы думали использовать Element Traversing API и новые ДОМ-методы типа insertAdjacentHTML, в некоторых браузерах прирост был до 80-90%, но в некоторых случаях размер, в других, опасность возникновения появления эджевых багов нас остановили.

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

    Тем не менее, сейчас, мы разрабатываем возможность автоматического тестирования скорости после каждого коммита, это позволит нам видеть регрессии сразу после их возникновения, надеюсь мы успеем это сделать ко времени выхода 2.1/1.11.
    • +1
      Во-первых, jQuery в первую очередь я бы характеризовал не как «медленную», а как «удобную». Пролизводительность в данном случае вторичный фактор.
      Во-вторых, я ничуть не рассматриваю данный тест, как основание для серьезных выводов. Это лишь повод задуматься. Если кто-то возьмется организовать серьезный тест с учетом всех сайд эффектов, то я буду рад посмотреть на его результаты. Правда по собственному опыту могу сказать, что при любой организации тестов «проигравшая» сторона всегда обвиняет организаторов в неправильном подходе к тестированию :)
      • 0
        Последнее вроде решается предложением сторонам участвовать в тестах самим. То есть ставятся задачи (желательно более-менее реальные) и представители сторон (может разработчики непосредственно, может «продвинутые пользователи», знающие сабж чуть ли не лучше разработчиков) решают их сами, скорее всего оптимальным для сабжа способом.

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

        И, кстати, наверное неплохо было бы гонять тесты на минимум двух конфигурациях железа: более-менее современной и достаточной для типовых задач в вебе и минимальной — увы, не редкость ситуация, когда на мощных девелоперских машинах всё летает, а на каком-нибудь нетбуке, бюджетном десктопе или вообще P4 с гигом памяти, который считался современным лет 10 назад (где нибудь в госконторах) нещадно тормозит, уходя, например, в своп, что приводит к потери части аудитории.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка