比较 $("#foo .bar") 和 $(".bar", "#foo") 的性能

Posted

技术标签:

【中文标题】比较 $("#foo .bar") 和 $(".bar", "#foo") 的性能【英文标题】:Comparing the performance of $("#foo .bar") and $(".bar", "#foo") 【发布时间】:2012-01-25 19:01:19 【问题描述】:

向下滚动查看getById.getByClassNameqSA 的比较!


如果我们想选择 ID 为 "foo" 的元素内的所有 "bar" 类元素,我们可以这样写:

$( '#foo .bar' )

或者这个:

$( '.bar', '#foo' )

当然还有其他方法可以实现,但是为了这个问题,我们只比较这两种方法。

那么,以上哪种方法效果更好呢? (哪个需要更少的时间来执行?)

我已经编写了这个性能测试:

(function() 
    var i;
    
    console.time('test1');
    for( i = 0; i < 100; i++ ) 
        $('#question-mini-list .tags');
    
    console.timeEnd('test1');
    
    console.time('test2');
    for( i = 0; i < 100; i++ ) 
        $('.tags', '#question-mini-list');
    
    console.timeEnd('test2');
)();

您必须在 Stack Overflow 起始页上的控制台中执行它。我的结果是:

火狐: 测试1:~90ms 测试2:~18ms

铬: 测试1:~65ms 测试2:~30ms

歌剧: 测试1:~50ms 测试2:~100ms

所以在 Firefox 和 Chrome 中,第二种方法要快很多倍——正如我所预料的那样。然而,在 Opera 中,情况正好相反。我想知道这里发生了什么。

能否请您在您的机器上运行测试并解释为什么 Opera 的性能不同?


更新

我编写了这个测试,以调查 Opera 的 qSA 是否真的超级快。事实证明,确实如此。

(function() 
    var i, limit = 5000, test1 = 'test1', test2 = 'test2';

    console.time( test1 );
    for( i = 0; i < limit; i += 1 ) 
        document.getElementById( 'question-mini-list' ).getElementsByClassName( 'tags' );
    
    console.timeEnd( test1 );

    console.time( test2 );
    for( i = 0; i < limit; i += 1 ) 
        document.querySelectorAll( '#question-mini-list .tags' );
    
    console.timeEnd( test2 );
)();

同样,您必须在 Stack Overflow 起始页上的控制台中运行此代码。我使用了 IE9 的 Firebug Lite 小书签(因为该浏览器没有实现 console.time)。

所以,我比较了这个方法:

document.getelementById( 'A' ).getElementsByClassName( 'B' );

到这个方法:

document.querySelectorAll( '#A .B' );

我已经在每个浏览器中连续执行了五次上述脚本。算术平均值为:

(所有数字都以毫秒为单位。)

因此,第一种方法的性能在测试的浏览器中几乎相同(16-36ms)。然而,虽然 qSA 与第一种方法相比要慢得多,但在 Opera 中它实际上更快!

所以,qSA优化是可能的,不知道其他浏览器在等什么……

【问题讨论】:

test1: 73ms, test2: 11ms。 Opera 是一个奇怪的浏览器,我不知道它为什么会滞后。 @Blender 请增加循环限制。我的笔记本电脑真的很慢,所以我选择了 100。尝试 1000。(小于4ms 的结果不可靠...) 您是否考虑过包含document.getElementById('foo').getElementsByClassName('bar') 以确保完整性? 不同的实现有不同的优化。要做什么? ;) 对于测试jsperf.com会是更好的选择。 【参考方案1】:

如果浏览器支持querySelectorAll,并且如果您传递了一个有效的选择器(没有自定义的非 CSS 选择器),jQuery/Sizzle 将避免使用基于 javascript 的 Sizzle 引擎。

这意味着您最终是在比较 querySelectorAll 的实现,假设您正在测试支持它的浏览器。

jQuery 或 Sizzle 还使用了其他优化,因此在不同浏览器中比较不同类型的 DOM 选择时很棘手。

Opera 的性能结果似乎是因为他们有一个非常优化的querySelectorAll 实现。 qSA 是一种相对较新的方法,与 getElementsByTagName 等旧方法相比,在某些浏览器中的优化还不够。

【讨论】:

嗯,qSA 并不是那么新。它从永远在 Chrome 中,从 3.5 开始在 Firefox 中,从 8 开始在 IE 中。浏览器有足够的时间来优化它。为他们感到羞耻(看看我上面的新测试)。 @ŠimeVidas:是的,新的只是相对于其他一些方法。但你是对的。在我看来,他们有足够的时间进行优化。我相信他们最终会回来的。 @ŠimeVidas:我将提出另一种可能性。由于您的测试对结果没有任何作用,我想知道优化是否根本不打扰 DOM 选择。也许它调用了该方法,但放弃了搜索。这可以解释为什么第一个测试需要更长的时间(两个函数调用)。这当然是纯粹的猜测。 @ЖΞЖ 我可以确认 Opera 进行了查询。我已经“在实践中”测试了这两个查询 - 请参阅此处的代码:jsfiddle.net/hfNTK/1 如果您在 Opera 中执行最后一部分,您会看到标签确实有蓝色背景,并且执行速度非常快.. . @ŠimeVidas: 嗯...但是您的测试的计时部分不使用选择的结果,并且页面上没有任何元素。您是否粘贴了正确的链接?【参考方案2】:

赢家是……

测试 3 $('#question-mini-list').find('.tags');

test1:25ms test2:19ms test3:10ms

您建议的两种方法不等效。

测试 1:Sizzle 从右到左解析(不要要求它搜索页面上的任何元素,然后限制为一个 ID)。

测试2:使用字符串作为上下文一般是没有用的,使用元素作为上下文。

测试 3:查找具有 id 的元素非常快。一旦你到了那里,就可以轻而易举地专注于给定类的一个项目。

【讨论】:

"Sizzle 从右到左解析" - 我很想有一个来源。 Sizzle 有一个优化,如果字符串以 id 选择器开头,it uses that first,那么它们应该是等价的。 jQuery 在内部将$('.bar', '#foo') 转换为$('#foo').find('.bar')。我认为它们是等价的。后者当然要快一些。 @ŠimeVidas: Click here 并查看倒数第二条评论。我还在视频中听到 Resig 谈到了这一点。我看看能不能再找到它。 @Sinetheta 问题不是最好的选择方式,而是为什么 Opera 与其他浏览器的行为不同。【参考方案3】:

作为参考,这要快 30 倍:

document.getElementById("foo").getElementsByClassName("bar");

参见 jsPerf:http://jsperf.com/jquery-selector-variations/3。这需要一个 shim 才能在旧版本的 IE 中工作。

虽然 jQuery 非常有用,但如果速度极快,它并不总是最适合这项工作的工具。

【讨论】:

那个“Plain JS”栏毁了图表:P @ŠimeVidas - 是的,我也注意到了。我没想到会有如此戏剧性的差异。如果您只想要这些,您可以返回到以前的版本。有点显示有多少开销,解析通用选择器和一般的 jQuery 对象都可能存在。

以上是关于比较 $("#foo .bar") 和 $(".bar", "#foo") 的性能的主要内容,如果未能解决你的问题,请参考以下文章

@ManyToMany(mappedBy = "foo")

为啥 `"foo".bar = 42;` 在 ES6 的严格模式下会抛出 `TypeError`?

linux的makefile中":="与"?="有啥区别

jquery 冒号转义 为啥双斜杠

理解 "栈" "队列","堆"(后进先出)

“foo = bar || baz”的惯用 Clojure 是啥?