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