jQuery对象:缓存还是不缓存?

7

我在编写Javascript代码时遇到了一些问题,因为有时我需要在同一个函数中多次访问同一个DOM元素。这里提供了一些理由参考

从性能的角度来看,是最好只创建一次jQuery对象并对其进行缓存,还是随时根据需要创建相同的jQuery对象呢? 例如:

function(){
  $('selector XXX').doSomething(); //first call
  $('selector XXX').doSomething(); //second call
  ...
  $('selector XXX').doSomething(); // n-th call
}

或者

function(){
  var  obj = $('selector XXX');
  obj.doSomething(); //first call
  obj.doSomething(); //second call
  ...
  obj.doSomething(); // n-th call       
}

我猜答案可能取决于“n”的值,所以假设n是一个“小”数(例如3),然后是一个中等数(例如10),最后是一个大数(例如30,就像在for循环中用作比较对象)。谢谢。

假设选择器的结果在代码执行期间不会改变,那么完全没有理由不缓存选择器。 - Anthony Grist
这个答案与其他答案非常不同。你是唯一一个没有建议缓存的人。你对其他答案有什么看法? - JeanValjean
3
我认为他的意思和我们一样 - 他说“没有理由不缓存” - 双重否定 => 我们应该缓存 :). 干杯! - Alex Ciminian
6个回答

8

如果 n 大于 1,则最好缓存元素,或将操作链接在一起(对于大多数 jQuery 操作,您可以使用 $('#something').something().somethingelse();,因为它们通常返回包装的集合本身)。顺便说一下,惯例是用美元符号 $ 开头命名缓存变量,以便在代码后期清晰地了解您正在执行的是 jQuery 集合操作。因此,您会看到很多人这样做:var $content = $('#content');,然后稍后使用 $content.find('...');


3
这个问题的出发点是我的开发通常用Java,在Java中,“缓存”一个对象意味着要保留“大量”的内存。因此,我想知道关于性能和内存消耗方面的情况。例如,如果我将document存储在一个var中,会消耗很多内存吗? - JeanValjean
@JeanValjean 如果你直接使用 var x = document,你不会分配比对象引用所需更多的内存。由 document 访问的对象是单例的。此外,var x = jQuery(document) 返回类似于一个 jQuery 对象的成员,其中包含仅为 document 的数组,这几乎比普通对象引用更昂贵(实际上,jQuery 本身可能将 jQuery(document) 处理为特殊情况)。要获取使用“大量”内存的 jQuery 对象,请使其匹配许多元素:jQuery('*') - binki

5
第二种方法更好。最重要的是,它更干净。将来如果你想改变选择器,只需在一个地方进行修改即可。否则你需要在N个地方进行修改。
其次,它应该更高效,尽管用户只有在特别多的dom或者经常调用该函数时才能察觉到。

2
如果你从不同的角度来看这个问题,正确的答案就很明显了。
在第一种情况下,你需要在每个出现的地方都进行选择逻辑。如果你更改元素的名称,你必须更改每一个出现的地方。这应该足以让你不要这样做。现在你有两个选项 - 你可以缓存元素的选择器或元素本身。使用元素作为对象比使用名称更有意义。
就性能而言,我认为影响是微不足道的。可能你会能够找到这个特定用例的测试结果:缓存jQuery对象与始终重新选择它们。如果你有一个大的DOM并且做了很多查找,性能可能成为一个问题,但你需要自己判断是否是这种情况。
如果你想准确地知道你的对象占用了多少内存,你可以使用 Chrome Heap Profiler并检查那里。我不知道其他浏览器是否提供类似的工具,而且在IE的情况下,实现可能会有很大的性能差异,但这可能会满足你的好奇心。
我认为你应该使用第二个变量,将选择结果存储在一个对象中,不仅可以提高性能,而且可以尽可能减少重复逻辑。
至于缓存$(this),我同意Nick Craver的答案。正如他所说,你还应该尽可能使用链接-这样可以清理你的代码并解决你的问题。

你的编辑回答了我向Paolo Bergantino提出的问题。谢谢。 - JeanValjean
你认为调用 $(this) 可能会重复吗?还是应该将其缓存起来? - JeanValjean
嗯!我不确定这个!可能 $(this) 总是可用的,不必使用选择器来检索它。因此,访问缓存变量或访问 $(this) 的成本应该是相同的!不是吗?在这种情况下,缓存 $(this) 不会提高性能(也许可以节省一些代码位)。 - JeanValjean
1
@jonny_cage:jQuery 将要进行的第一个检查之一是查看传递的选择器是否为 DOMElement 对象,如果是,则简单地返回包装的元素。我不介意人们缓存 this,因为习惯缓存选择器是很好的,但在 this 的情况下,你选择哪个是无关紧要的。 - Paolo Bergantino

1

我几乎总是更喜欢缓存jQuery对象,但其好处取决于您使用的选择器。如果使用ID,则好处远少于使用其他类型的选择器。而且,并非所有选择器都是相同的,因此在编写选择器时要牢记这一点。

例如:$('table tr td') 是一个非常糟糕的选择器。尝试使用上下文或 .find() 方法,它会产生很大的不同。

我喜欢在我的代码中放置定时器来查看其效率。

var timer = new Date(); 
// code here
console.log('time to complete: ' + (new Date() - timer));

大多数缓存对象的执行时间不到2毫秒,而全新的选择器需要更长的时间,因为你首先要找到元素,然后执行操作。


0
在JavaScript中,函数通常是短暂的 - 特别是当它们由浏览器托管时。但是,函数的作用域可能会超出函数的生命周期。例如,当您创建闭包时就会发生这种情况。如果您想防止jQuery对象被长时间引用,可以在完成该变量的引用或使用间接引用创建闭包时将null分配给任何引用它的变量。例如:
var createHandler = function (someClosedOverValue) {
    return function () {
        doSomethingWith(someClosedOverValue);
    };
}

var blah = function () {
    var myObject = jQuery('blah');

    // We want to enable the closure to access 'red' but not keep
    // myObject alive, so use a special createHandler for it:
    var myClosureWithoutAccessToMyObject = createHandler('red');

    doSomethingElseWith(myObject, myClosureWithoutAccessToMyObject);

    // After this function returns, and assuming doSomethingElseWith() does
    // not itself generate additional references to myObject, myObject
    // will no longer have any references and be elligible for garbage
    // collection.
}

因为 jQuery(selector) 可能需要运行昂贵的算法,甚至需要在复杂表达式中遍历 DOM 树,这些都无法直接由浏览器处理,所以最好缓存返回的对象。此外,正如其他人所提到的,为了代码清晰度,最好缓存返回的对象,以避免多次输入选择器。即,DRY 代码通常比 WET 代码更易于维护。

然而,每个 jQuery 对象都有一定的开销。因此,在全局变量中存储大型 jQuery 对象数组可能是浪费的,除非您确实需要操作大量这些对象并仍将它们视为不同的对象。在这种情况下,您可以通过缓存 DOM 元素的数组并使用 jQuery(DOMElement) 构造函数来节省内存,当迭代它们时,这基本上是免费的。

尽管,正如人们所说,只有通过对不同方法进行基准测试才能知道适合您特定情况的最佳方法。即使理论看起来很正确,预测现实也很困难 ;-).


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