"$(this)" 的成本是多少?

56

这里的人经常建议缓存从DOM元素创建的jQuery对象,例如使用以下代码:

$('#container input').each(function() {
    $(this).addClass('fooClass');
    $(this).attr('data-bar', "bar");
    $(this).css('background-color', 'red');
});
  • 缓存jQuery对象真的能提高我们代码的性能吗?
  • 当将DOM元素传递给jQuery构造函数时,发生了什么“幕后”?

你应该总是使用缓存,但在这个特定的例子中,你甚至不需要这样做。只需利用jQuery链式操作:$(this).addClass('fooClass').attr('data-bar', "bar").css('background-color', 'red'); - Jose Rui Santos
4个回答

54

在jQuery的标签信息中出现了以下警告:

jQuery函数$()是昂贵的。 反复调用它非常低效。

嗯...这只对字符串选择器有效,因为它们需要使用正则表达式进行解析以找出它们的内容:

quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/

如果字符串是选择器(除了id),那么jQuery会使用其昂贵的find函数遍历DOM以查找匹配项:

} else if ( !context || context.jquery ) {
    return ( context || rootjQuery ).find( selector );
}

所以,是的,它很昂贵,但这只对选择器有效!

如果我们传递一个 DOMElement,那么 jQuery 只会将 DOMElement 参数保存为新创建的 jQuery 对象的上下文,并将上下文的长度设置为 1:

// Handle $(DOMElement)
if ( selector.nodeType ) {
    this.context = this[0] = selector; // Selector here is a DOMElement
    this.length = 1;
    return this;
}

我在jsPerf进行了一些测试,并发现缓存jQuery对象确实只会有很小的影响:

下面描述的条形图

在Chrome中,它只慢了7%。(在IE中,这个影响稍微更显著:12%。)


无论哪种情况,您每次都至少节省一个函数调用。 - Christoph
比较不正确...性能有很大的差异。 - Adi Darachi

14

回答你的第二个问题,看一下源代码

// Handle $(DOMElement)
if ( selector.nodeType ) {
    this.context = this[0] = selector;
    this.length = 1;
    return this;
}

1
这是一个很好的小应用程序,可以查看源代码:http://james.padolsey.com/jquery/#v=git&fn=jQuery.fn.init - Joe
3
现在一个字符串选择器显然会有一个非常不同的图形。 - Joe
感谢SLaks指引我查看jQuery源代码。我不知道为什么我一开始没有自己去做这件事。 - gdoron

10

关于性能差异,如果您要直接比较两者,最好删除可能会影响结果的任何额外代码,例如DOM选择和其他与其无直接关联的方法。

http://jsperf.com/this-cost/2

enter image description here

在更实际的情况下,如您的测试所示,相对差异很小。

还要记住的一件事是,每次创建jQuery对象都需要为其分配内存,这会增加垃圾回收器需要处理的工作量。

因此,我认为人们建议缓存的原因是出于某种原则立场。虽然通常不会有明显的影响,但确实需要做一些额外的工作,从而增加了一些可以轻松避免的开销。


这个比较要好得多,比已接受答案的比较更好。 - Adi Darachi

8
这里所有的运行时性能测试都忽略了另一个重要考虑因素:网络带宽。
$(this)缓存到本地变量中通常会减小脚本的大小,特别是在缩小后(因为this无法从四个字符中减少)。
请考虑:
function hello(text) {
    $(this).attr();
    $(this).css();
    $(this).data();
    $(this).click();
    $(this).mouseover();
    $(this).mouseleave();
    $(this).html(text);
}
hello('Hello world');

Closure编译器的压缩输出为:
function hello(a){$(this).attr();$(this).css();$(this).data();$(this).click();$(this).mouseover();$(this).mouseleave();$(this).html(a)}hello("Hello world");

这样可以节省39个字节(20%)。现在考虑以下内容:
function hello(name) {
    var $this = $(this);
    $this.attr();
    $this.css();
    $this.data();
    $this.click();
    $this.mouseover();
    $this.mouseleave();
    $this.html(name);
}
hello('Hello world');

压缩后的输出是:
function hello(b){var a=$(this);a.attr();a.css();a.data();a.click();a.mouseover();a.mouseleave();a.html(b)}hello("Hello world");

这样做可以节省74字节(37%),几乎将我们的字节节约翻了一番。显然,对于大型脚本的真实世界节约将会更低,但您仍然可以通过缓存来显著减少脚本的大小。

实际上,只有缓存$(this)会使得利润更大。您可以获得微小但可测量的运行时性能提升。更重要的是,您可以减少在网络传输中的字节数,并且这直接转化为更多的收益,因为页面加载速度越快,销售额就越高

从这个角度看,您实际上可以说对于反复使用$(this)而不进行缓存存在着可量化的成本


иҷҪ然жӣҙеӨҡжҳҜеӣһзӯ”дёәд»Җд№Ҳеә”иҜҘзј“еӯҳthisиҖҢдёҚжҳҜ$(this)пјҢеӣ дёәжӮЁеҸҜд»ҘдҪҝз”Ёthis.value; this.tagName; this.className; this.nodeType; this....иҺ·еҫ—зӣёеҗҢзҡ„з»“жһңгҖӮ - gdoron
@gdoron,使用原始DOM和jQuery方法之间存在很大的区别;它们并不总是可互换的。 (addClassdata,动画...) 除此之外,在 var a = $(this); a...; a...;var a = this; $(a)...; $(a)...; 之间仍然存在每个调用3字节的差异。 - josh3736
如果你正在使用gzip压缩文件,你会发现由于缓存的原因,文件大小可能会稍微变大。在你的例子中,只有111字节和115字节之间的微小差异,但这凸显了这一点。我不知道为什么会这样,但我经常发现这种情况。 - cliffs of insanity
@user1370958,压缩文件仍然更小,只是节省的空间更少了。在上面的两个例子中,仅缩小的节省空间为20%和37%;缩小+压缩的节省空间为7%和12%。虽然压缩后的内容可能比原始内容更大,但这通常仅发生在非常小的文件(<50字节)中。 - josh3736
3
是的,我的意思只是说,如果您使用gzip压缩,那么像“this”这样的缓存某些内容可能会导致文件比使用非缓存“this”的代码版本进行gzip压缩后更大。它们肯定都比原始文件小。重要的不是节省的百分比,因为起点不同,而是需要测量最终文件的字节大小。 - cliffs of insanity

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接