Side navigation
#9525 closed enhancement (duplicate)
Opened June 06, 2011 03:04PM UTC
Closed June 08, 2011 04:58PM UTC
Last modified June 11, 2011 07:42PM UTC
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.
Attachments (0)
Change History (9)
Changed June 08, 2011 04:58PM UTC by comment:1
resolution: | → duplicate |
---|---|
status: | new → closed |
Changed June 08, 2011 05:08PM UTC by comment:3
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.
Changed June 08, 2011 06:31PM UTC by comment:4
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.
Changed June 08, 2011 07:20PM UTC by comment:5
component: | unfiled → core |
---|---|
priority: | undecided → low |
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.
Changed June 08, 2011 09:32PM UTC by comment:6
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?
Changed June 09, 2011 03:56PM UTC by comment:7
_comment0: | 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 = $(); \ 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. \ → 1307635072091699 |
---|
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.
Changed June 09, 2011 04:24PM UTC by comment:8
_comment0: | 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). \ \ 2. 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. → 1307636729703408 |
---|---|
_comment1: | 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). \ \ 2. 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. → 1307637256652669 |
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).
2. 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.
Changed June 11, 2011 07:42PM UTC by comment:9
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.
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.