15

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?

7 Answers 7

12

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).

Sign up to request clarification or add additional context in comments.

4 Comments

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.
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 :)
@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: 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.
3

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.

6 Comments

So this would work as well? :) Also I now need to do some profiling to understand whichever is faster.
@badp: Yes. I don't think there is much difference on obj[i] and obj.slice(i, i+1)... See the example here jsfiddle.net/m7pKk
@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...
Indeed, taking BrunoLM's implementation of each2 and changing it to only call eq(i) once roughly halves the execution time -- source.
@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?
|
2

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.

Comments

1

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.

Comments

0

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.

4 Comments

I'll need to call a function per hit that is more complicated than just a .text() call :)
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"?
yes, if I want to, say, apply a mathematical function to the value in each ".foo". Same function, different input, different output.
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.
-1

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)

1 Comment

This is in no meaningful way different. I still need to make jQuery objects.
-1

Try

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

1 Comment

The $() constructor is still there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.