tags:

views:

115

answers:

7

The following works as expected:

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

...because first() returns a jQuery object.

However, if I want to work with the text() method for all matches, I need to do:

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

...because each() gives you DOM objects.

What is the design reason behind this baffling difference? How can I avoid having to build a jQuery object for each match?

A: 

Did you see .each vs jQuery.each

You should be able to do the following:

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

per the first link. Try $(this) instead of $(obj)

Anon
This is in no meaningful way different. I still need to make jQuery objects.
badp
A: 

Try

$(".foo").each( function(idx, obj) {
  $(this).text("hi!")
  }
)
GôTô
The `$()` constructor is still there.
badp
+7  A: 

Possibly due to performance reasons related to looping over large collections? If you only need the DOM objects, then you save cycles. If you need the jQuery object, then you can easily get that.

I typically don't provide the 2nd parameter to each, so I can use $(this).

Kaleb Brasee
Exactly what I was going to say, plus if first() didn't return a jQuery object, you'd have to use $($(selector).first()), which somewhat conflicts with jQuery's aim of being concise.
Jon Cram
Okay, I was under the impression that `$(".foo")` made jQuery objects _to begin with_ and they were stripped down to "basic" DOM objects when passed through `each()`. I guess that's not quite what's going on here :)
badp
@Jon - That's not correct. `$(selector).first()` *does* return a jQuery object. You shouldn't wrap it again with `$($(selector).first())`. The only jQuery method (to my knowledge) that returns a plain DOM element is `.get( 0 )` when you pass it a number. Without the number argument, you get an Array of DOM elements.
patrick dw
@patrick dw: I was indicating what you'd have to do if first() didn't return a jQuery object, suggesting why it makes sense for first() to return a jQuery object.
Jon Cram
@Jon - Ah yes, I read too quickly. :o)
patrick dw
A: 

I believe that since jQuery uses wrapper objects, the first method uses the original wrapper, and just removes all but the first element in the wrapper, thus keeping it being a jQuery object.

However, if they were to feed in a jQuery object for each of the nodes for the each() function, they would be incurring that overhead of creating the wrapper for each one. And since you don't necessarily want that wrapper object, it would incur unnecessary overhead.

Thomas
+1  A: 

Internally jQuery call this for $("sel").each(function(){});

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

And the eq is a simple slice:

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

So you can create a new each function that instead of object[name] will do a object:eq(i)

$("*").slice(1,2).toSource() == $("*").eq(1).toSource();

So to create your own 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
});

It seems that each3 is faster than each2 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 )
   }
};

See this example on jsFiddle with performance measurement.

BrunoLM
So [this](http://gist.github.com/629941) would work as well? :) Also I now need to do some profiling to understand whichever is faster.
badp
@badp: Yes. I don't think there is much difference on `obj[i]` and `obj.slice(i, i+1)`... See the example here http://www.jsfiddle.net/m7pKk/
BrunoLM
@BrunoLM, actually `$(".foo")[i]` returns a DOM object whereas `$(".foo").eq(i)` gives a jQuery object. The pretty much the point here, and given the implementation of `eq` through slices I'm almost led to believe `$($(".foo")[i])` would be faster...
badp
@badp: Indeed. http://www.jsfiddle.net/m7pKk/2/
BrunoLM
Indeed, taking BrunoLM's implementation of each2 and changing it to only call `eq(i)` once roughly halves the execution time -- [source](http://www.jsfiddle.net/m7pKk/5/).
badp
@BrunoLM: Are you sure the list always contains jQuery objects? If it does, that leads back to the original question: why doesn't jQuery implement it that way in the first place?
hheimbuerger
@hheimbuerger: The fastest way to make a `each` return a jQuery object is to use `$(obj)` which can be done inside the callback function. Without this call it is faster. That's why it was done this way.
BrunoLM
+1  A: 

There's the obvious performance hit that would be taken per iteration. Creating a new jQuery object each iteration would be much slower and probably noticeable over large collections. Quite often, you don't need the added convenience of the wrapped object, especially when accessing single attributes or properties. All too often you see cycle wasting code like $(this).is(":checked") instead of this.checked.

Aside from that, though, I would say that it's because it makes sense. A jQuery object typically represents a collection of DOM objects that can be any number in size. Sometimes jQuery is used purely for its selector support and event binding and not much beyond that. It doesn't make much sense to return a collection containing a single element when iterating over a collection of more elements. It makes much more sense to return a single item that you're more likely to need, the DOM element, then you can wrap it with jQuery if you want the added features. This also keeps it in line with iterating over NodeList and other types of collection.

Andy E
A: 

Probably because, in your example there's no reason to even use each. Instead of:

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

Just use:

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

Everything is automatically plural when dealing with jQuery sets.

darkporter
I'll need to call a function per hit that is more complicated than just a `.text()` call :)
badp
But that gets right to the issue -- if you need to do something unique for each item in the set, does it even make sense to select them all together with ".foo"?
darkporter
@darkporter yes, if I want to, say, apply a mathematical function to the value in each ".foo". Same function, different input, different output.
badp
Ah, in that case I'd suggest passing a function into text(), but another poster probably already suggested that. I was thinking you might be doing like `$(this).text($(this).hasClass("selected") ? "selected" : "")` or something silly, in which case I'd have suggested just doing jQuery(".foo.selected") or whatever.
darkporter