Подтвердить что ты не робот

Почему "this" эффективнее, чем сохраненный селектор?

Я делал этот тестовый пример, чтобы узнать, как много с помощью селектора this ускоряет процесс. Выполняя это, я решил попробовать также предварительно сохраненные переменные элемента, предполагая, что они будут еще быстрее. Использование элементной переменной, сохраненной до теста, кажется самым медленным, вполне к моей путанице. Я, хотя только для того, чтобы "найти" элемент однажды, очень ускорит процесс. Почему это не так?

Вот мои тесты от самого быстрого до самого медленного, в случае, если кто-то не может его загрузить:

1

$("#bar").click(function(){
    $(this).width($(this).width()+100);
});
$("#bar").trigger( "click" );

2

$("#bar").click(function(){
    $("#bar").width($("#bar").width()+100);
});
$("#bar").trigger( "click" );

3

var bar = $("#bar");
bar.click(function(){
    bar.width(bar.width()+100);
});
bar.trigger( "click" );

4

par.click(function(){
    par.width(par.width()+100);
});
par.trigger( "click" );

Я бы предположил, что порядок будет идти 4, 3, 1, 2, в соответствии с которым нужно использовать селектор, чтобы "находить" переменную чаще.

ОБНОВЛЕНИЕ: У меня есть теория хотя я бы хотел, чтобы кто-то подтвердил это, если это возможно. Я предполагаю, что при щелчке он должен ссылаться на переменную, а не только на элемент, который замедляет ее.

4b9b3361

Ответ 1

Исправлен тестовый пример: http://jsperf.com/this-vs-thatjames/10

TL; DR: количество обработчиков кликов, выполняемых в каждом тесте, растет, потому что элемент не является reset между тестами.

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

// I am using Benchmark.js API as if I would run it in the d8.
Benchmark.prototype.setup = function() {
  function multiply(x,y) {
    return x*y;
  }
};

var suite = new Benchmark.Suite;
suite.add('multiply', function() {
  var a = Math.round(Math.random()*100),
      b = Math.round(Math.random()*100);

  for(var i = 0; i < 10000; i++) {
     multiply(a,b);
  }
})

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

Прежде всего, вы не тестируете селекторов. Ваш тестовый код выполняет: ноль или более селекторов, в зависимости от теста, создание функции (которая в некоторых случаях является закрытием, другие - нет), назначение в качестве обработчика кликов и запуск системы событий jQuery.

Кроме того, элемент, который вы тестируете, меняется между тестами. Очевидно, что ширина в одном тесте больше, чем ширина в тесте раньше. Но это не самая большая проблема. Проблема в том, что элемент в одном тесте связан с обработчиками X-кликов. Элемент в следующем тесте имеет обработчики кликов X + 1. Поэтому, когда вы запускаете обработчики кликов для последнего теста, вы также запускаете обработчики кликов, связанные во всех тестах раньше, делая его намного медленнее, чем предыдущие тесты.

Я установил jsPerf, но имейте в виду, что он все еще не проверяет производительность селектора. Тем не менее, самый важный фактор, искажающий результаты, устраняется.


Примечание. Есть несколько slides и видео о хорошем тестировании производительности с помощью jsPerf, ориентированном на общие ошибки, которых следует избегать. Основные идеи:

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

Ответ 2

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

Если вы посмотрите на вывод консоли для этого измененного теста: http://jsperf.com/this-vs-thatjames/8

Вы увидите, сколько слушателей событий подключено к объекту #bar. И вы увидите, что они не удаляются в начале для каждого теста.

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

Ответ 3

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

$("#bar").click(function(){         
    $(this).width($(this).width()+100); // Only has to check the function call
});                                     // each time, not search the whole memory

в отличие от

var bar = $("#bar");
...
bar.click(function(){
    bar.width(bar.width()+100);         // Has to search the memory to find it 
});                                     // each time it is used

Как сказали zerkms, разыменование (необходимость поиска ссылки на память, как я описал выше) имеет некоторый, но небольшой эффект на производительность

Таким образом, основным источником медлительности в отличии от проведенных тестов является тот факт, что DOM не является reset между каждым вызовом функции. На самом деле сохраненный селектор выполняет примерно так же быстро, как this

Ответ 4

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

Ответ 5

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

Когда вы выполняете $(...), вы вызываете конструктор jQuery и создаете новый объект, который хранится в памяти. Однако, когда вы ссылаетесь на существующую переменную, вы не создаете новый объект (duh).

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

Еще раз, я не уверен. Очень интересный тест btw!