为什么"这个"比保存的选择器更有效?

时间:2014-03-23 20:09:22

标签: javascript jquery

我正在this test case查看使用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,其中必须使用选择器"找到"变量更常见。

更新:我有theory,但我希望有人在可能的情况下验证这一点。我猜测在点击时,它必须引用变量,而不仅仅是元素,这会减慢它的速度。

5 个答案:

答案 0 :(得分:9)

修正了测试用例:http://jsperf.com/this-vs-thatjames/10

TL; DR:每次测试中执行的点击处理程序数量增加,因为测试之间没有重置元素。

测试微优化的最大问题是你必须非常小心你正在测试的东西。在许多情况下,测试代码会干扰您正在测试的内容。这是来自Vyacheslav Egorov的一个example测试,“证明”乘法在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和一个video关于使用jsPerf进行良好的性能测试,重点关注应该避免的常见陷阱。主要想法:

  • 不在测试中定义功能,在设置/准备阶段执行
  • 保持测试代码尽可能简单
  • 比较做同样事情或事先做好事情的事情
  • 测试您要测试的内容,而不是设置代码
  • 隔离测试,在每次测试之后/之前重置状态
  • 没有随机性。如果你需要的话嘲笑它
  • 了解浏览器优化(死代码删除等)

答案 1 :(得分:6)

你并没有真正测试不同技术之间的表现。

如果您查看此修改测试的控制台输出: http://jsperf.com/this-vs-thatjames/8

您将看到有多少事件侦听器附加到#bar对象。 你会发现每次测试都不会删除它们。

因此,以下测试将始终变得比以前的测试慢,因为触发器函数必须调用所有先前的回调。

答案 2 :(得分: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所说,解除引用(必须按照上面的描述查找内存引用)对性能有some but little effect

因此,您执行的测试的差异缓慢的主要原因是DOM不会在每个函数调用之间重置。实际上,保存的选择器的执行速度与this

一样快

答案 3 :(得分:2)

看起来您获得的性能结果与代码无关。如果你看these edited tests,你可以看到在两个测试中(第一个和最后一个)使用相同的代码会产生完全不同的结果。

答案 4 :(得分:-4)

我不知道,但如果我不得不猜测我会说这是由于并发和多线程。

执行$(...)时,调用jQuery构造函数并创建一个存储在内存中的新对象。但是,当您引用现有变量时,不会创建新对象(duh)。

虽然我没有引用来源,但我相信每个javascript事件都会在自己的线程中调用,因此事件不会互相干扰。通过这种逻辑,编译器必须锁定变量才能使用它,这可能需要时间。

再一次,我不确定。非常有趣的测试btw!