Bug Tracker

Opened 9 years ago

Closed 9 years ago

Last modified 9 years ago

#9525 closed enhancement (duplicate)

performance: a really fast single-element $() wrapper?

Reported by: shadedecho Owned by:
Priority: low Milestone: 1.next
Component: core Version: 1.6.1
Keywords: Cc:
Blocked by: Blocking:

Description

A common (and major) performance anti-pattern in jquery is the oft-seen $(elem) single-element collection wrapping, which invokes the performance-heavy $() constructor for the special case where all someone wants is the special jquery decorations on top of a single element (don't need collection/stack management).

For example:

http://jsperf.com/scriptjunkie-premature-3/6

Inside jquery functions like .each(), it's extremely common to have the pattern of $(this) wrapping, because someone wants to call the special jquery operations/decorations on the object instead of doing bare DOM element manip. It's quite rare in that pattern for someone to be setting up a jquery collection which they will add elements to, etc. They really just need (usually) a single-element collection, and a faster wrapper would be very nice for that purpose.

As an example:

http://jsperf.com/dollar-vs-dollar-underscore

Ext.fly() is apparently similar (disclaimer, I have no Ext experience to speak of). The idea is to have a quick and fast $()-like constructor for creating a jquery object wrapper around only a single-element. In other words, no need for all the stack manipulation stuff (ostensibly seems is what slows down $() construction).

In my jsperf test, I created $_(). I think it's possible that $() might be special-cased in some way, but I honestly think that such a constructor should be a special name, because the single-element collection is creates is special, and has caveats (like no stack manipulation, etc).

Also, *my* $_() is quite flawed, as doing this from the outside is (afaict) impossible without the caveat as noted. But it seems like internally jquery could easily and quickly create the object wrapper, and simply assign the first and only element via the elem.context = elem[0] = obj approach.

Change History (9)

comment:1 Changed 9 years ago by dmethvin

Resolution: duplicate
Status: newclosed

I do not think this is a feature to be added to jQuery, but a pattern that jQuery users (including core) could follow to improve performance. There are too many gotchas to do this by default.

comment:2 Changed 9 years ago by dmethvin

Duplicate of #9481.

comment:3 Changed 9 years ago by shadedecho

just curious, why would having a separate $_() (or something else) type of constructor, which the dev would be explicitly using to get a single-elem collection (just the wrapper, not the stack) have too many gotchas?

By contrast, I think trying to automatically do it inside of $() would have way more gotchas.

comment:4 Changed 9 years ago by shadedecho

FWIW, I do NOT think this is a duplicate of that other ticket, though they have similar goals. I do not want the caveat that each call to $_() or $.fly() gives a single-elem collection that shares from a single global $() collection. This is the caveat I noted in my performance benchmark on jsperf.com, and it's quite undesirable.

What I'm asking for with this ticket is to be able to create a jquery wrapper object (with all the jquery helper methods) around a single object, but have it not do any of the internal stack management stuff (that apparently is the mostly costly perf wise).

It would have to be an explicit and separate API, or at the least require a flag param to $(), such that I as a dev and expressing that I want to opt out of the "collection" part of the jquery wrapper, and give me only the single-elem wrapper, because I don't intend to use this as a collection.

In fact, the jquery-like object I'm suggesting would possibly not even need any of the jquery methods for explicitly dealing with the stack/collection, such as .end(), .find(), .add(), etc.

I would only need the helpers, like attr(), css(), bind(), etc.

comment:5 in reply to:  4 Changed 9 years ago by Rick Waldron

Component: unfiledcore
Priority: undecidedlow

What I'm asking for with this ticket is to be able to create a jquery wrapper object (with all the jquery helper methods) around a single object, but have it not do any of the internal stack management stuff (that apparently is the mostly costly perf wise).

Unfortunately, this change is simply not plausible.

comment:6 Changed 9 years ago by shadedecho

would you mind explaining why it's not plausible? If after a jquery collection is created, I can simply assign .context and [0] to my element, and effectively get what I want, why can't that be done as a shortcut for me, in a performant way, without doing the extra work?

also, is this really still just a duplicate of the other ticket?

comment:7 Changed 9 years ago by dmethvin

If you only need to do $(this) once, the overhead is not that great and there is no need for another constructor, especially if it has unusual gotchas people have to remember. The overhead only becomes important in loops. For that case you can use a pattern:

var $this = $(document);
this.each(function(){
    $this[0] = this;
    // carry on with $this
});

Like I said in the other ticket, I do think there could be a benefit to this pattern, even inside core itself, but I do not think we need to create a second-class-citizen constructor to handle this case.

Do you have some code that is bottlenecking on $(this)? If so I'd like to see it. Start a thread in the forum under the "Developing jQuery Core" section and we can take a look.

Last edited 9 years ago by dmethvin (previous) (diff)

comment:8 Changed 9 years ago by shadedecho

If you look at:

http://jsperf.com/dollar-vs-dollar-underscore

You'll see that your suggested snippet is in spirit the same thing as I already explored.

It works fine for the case where you only ever need to deal with one such single-element-wrapper-instance at a time, because that global $() instance can just be re-used over and over.

But, the major caveat comes if more than one such wrapper needs to be used. For instance, say inside an .each() loop, I need to wrap $(this) AND $(this.parentNode), and I need to use the two together at the same time. In the case of a solution where a global "flyweight" collection is just re-used, this falls apart completely, because they trample on each other.

So, in that case, being able to do $_(this) and $_(this.parentNode), or whatever, is strongly desirable. The awkward (and non-performant) workaround options are:

  1. use $this[0] = this to speed that operation up, but use the slower $(this.parentNode) for the other(s).
  1. only use one wrapper at a time, swapping back and forth as necessary, like:
$elem[0] = this;
var rel = $elem.attr("rel");
$elem[0] = this.parentNdoe;
var parent_rel = $elem.attr("rel") + "|" + rel;
$elem.attr("rel",parent_rel);
$elem[0] = this;
$elem.attr("rel", parent_rel);

And btw, I know my example is a little contrived, as wrapping this.parentNode is silly compared to just doing $this.parent(). But the spirit of this is that I have two different nodes that I need to access at the same time, and I would ideally like each of them to have the handy jquery wrappers around them. But neither of them am I using as a collection where I need the overhead of stack management.

$(A) and $(B) works fine for that purpose, except that they're much slower, compared to if I happen to already have two unused/idle collections sitting around, like $a and $b, that I can just do the quick assign like $a[0] = A and $b[0] = B.

So, ideally, what I'd like is a quick way to get a simple single-element collection for any object (as many as I need at a time), without the performance overhead of doing the collection/stack-management stuff.

Last edited 9 years ago by shadedecho (previous) (diff)

comment:9 Changed 9 years ago by dmethvin

Do you have some code that is bottlenecking on $(this)? If so I'd like to see it. Start a thread in the forum under the "Developing jQuery Core" section and we can take a look.

Note: See TracTickets for help on using tickets.