为什么使用 .each() 迭代 jQuery 对象不会返回 jQuery 对象?

15
以下内容按预期工作:
$(".foo").first().text("hi!")

因为 first() 返回的是一个jQuery对象。

然而,如果我想要使用text()方法处理所有匹配项,我需要这样做:

$(".foo").each( function(idx, obj) {
  $(obj).text("hi!")
  }
)

...因为each()提供的是DOM对象。

这种令人困惑的差异背后的设计原因是什么?我如何避免每次匹配都要构建一个jQuery对象?

7个回答

12

可能是由于与遍历大集合相关的性能原因?如果只需要DOM对象,则可以节省循环次数。如果需要jQuery对象,那么可以轻松获取。

通常我不会提供each的第二个参数,这样可以使用$(this)。


正是我想说的,而且如果first()没有返回一个jQuery对象,你就必须使用$($(selector).first()),这与jQuery简洁的目标有些冲突。 - Jon Cram
好的,我原本以为 $(".foo") 会生成 jQuery 对象,并在通过 each() 时将它们削减为“基本”的 DOM 对象。我猜这里并不完全是这样 :) - badp
@Jon - 这不正确。$(selector).first()确实返回一个jQuery对象。你不应该再次用$($(selector).first())包装它。据我所知,唯一返回纯DOM元素的jQuery方法是.get(0),当你传递一个数字时。没有数字参数,你会得到一个DOM元素数组。 - user113716
@patrick dw: 我是在说明如果 first() 没有返回一个 jQuery 对象,你将需要做什么,并建议为什么让 first() 返回一个 jQuery 对象是有意义的。 - Jon Cram

3

在内部,jQuery为$("sel").each(function(){});调用此函数。

if ( isObj ) {
    for ( name in object ) {
        if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
            break;
        }
    }
}

eq 则是一个简单的切片:

eq: function( i ) {
    return i === -1 ?
    this.slice( i ) :
    this.slice( i, +i + 1 );
}

所以,您可以创建一个新的each函数,而不是使用object[name],而是使用object:eq(i)
$("*").slice(1,2).toSource() == $("*").eq(1).toSource();

因此,要创建自己的each函数:
$.fn.each2 = function(callback)
{
   for ( var i = 0; i < this.length; ++i ) {
      callback.call( this.eq(i), i, this.eq(i) )
   }
};

$("*").each2(function(i, obj) {
    alert(obj); // now obj is a jQuery object
});

看起来each3each2更快http://www.jsfiddle.net/m7pKk/2/

$.fn.each2 = function(callback)
{
   for ( var i = 0; i < this.length; ++i ) {
       var jObj = this.eq(i);
      callback.call( jObj, i, jObj )
   }
};

$.fn.each3 = function(callback)
{
   for ( var i = 0; i < this.length; ++i ) {
       var jObj = $(this[i]);
      callback.call( jObj, i, jObj )
   }
};

点击此处在jsFiddle上查看性能测量的示例。


那么这个(http://gist.github.com/629941)也可以工作吗? :) 另外,我现在需要进行一些分析以了解哪个更快。 - badp
@badp:是的。我认为obj[i]obj.slice(i, i+1)没有太大区别... 在这里可以看到例子http://www.jsfiddle.net/m7pKk/ - BrunoLM
@BrunoLM,实际上$(".foo")[i]返回一个DOM对象,而$(".foo").eq(i)则返回一个jQuery对象。这就是重点,鉴于eq的实现是通过切片完成的,我几乎相信$($(".foo")[i])会更快... - badp
实际上,采用BrunoLM的each2实现,并将其更改为仅调用eq(i)一次,可以将执行时间大致减半--来源 - badp
@BrunoLM:你确定列表总是包含jQuery对象吗?如果是这样,那就回到了最初的问题:为什么jQuery一开始不是这样实现的呢? - Henrik Heimbuerger
@hheimbuerger:使each返回jQuery对象的最快方法是在回调函数内使用$(obj)。如果没有这个调用,它会更快。这就是为什么要这样做的原因。 - BrunoLM

2
每次迭代都会带来明显的性能损失。每次创建一个新的jQuery对象会更慢,对于大量的集合可能会很明显。通常情况下,您不需要包装对象的附加便利性,特别是在访问单个属性或属性时。往往会看到浪费循环代码,例如$(this).is(":checked")而不是this.checked
此外,我认为这是因为它有意义。一个jQuery对象通常表示可以包含任意数量的DOM对象的集合。有时候jQuery仅用于其选择器支持和事件绑定等功能。当迭代多个元素的集合时,返回包含单个元素的集合并不太合理。更合理的做法是返回您更可能需要的单个项目,即DOM元素,然后如果需要附加功能,则可以将其包装在jQuery中。这也使其与NodeList和其他类型的集合迭代保持一致。

1

我相信由于jQuery使用包装对象,第一个方法使用原始包装器,只是删除包装器中除第一个元素外的所有内容,因此仍然保持它是一个jQuery对象。

然而,如果他们为each()函数的每个节点提供一个jQuery对象,他们将会承担为每个节点创建包装器的开销。而且由于您不一定需要该包装器对象,这将会产生不必要的开销。


0

可能是因为在你的例子中甚至没有必要使用each。可以改为:

$(".foo").each( function(idx, obj) {
  $(obj).text("hi!");
)

只需使用:

$(".foo").text("hi!");

在处理 jQuery 集时,一切都自动成为复数。


1
我需要在每次点击时调用一个比.text()更复杂的函数 :) - badp
但这正好涉及到问题所在——如果您需要为集合中的每个项目执行不同的操作,使用“.foo”一起选择它们是否有意义? - jpsimons
是的,如果我想对每个“.foo”中的值应用数学函数,那么就可以使用相同的函数,不同的输入和不同的输出。 - badp
嗯,在那种情况下,我建议将一个函数传递给text(),但另一个贴子可能已经提出了这个建议。我想你可能在做类似于 $(this).text($(this).hasClass("selected") ? "selected" : "") 或者其他一些愚蠢的事情,如果是这种情况,我会建议只是做jQuery(".foo.selected")或其他类似的事情。 - jpsimons

-1

你看到了.eachjQuery.each的区别吗?

你应该能够做到以下几点:

$('li').each(function(index) {
    alert(index + ': ' + $(this).text());
});

根据第一个链接。尝试使用$(this)而不是$(obj)

这在任何有意义的方式上都没有区别。我仍然需要创建jQuery对象。 - badp

-1

尝试

$(".foo").each( function(idx, obj) {
  $(this).text("hi!")
  }
)

$() 构造函数仍然存在。 - badp

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