So when writing filters for jQuery (i.e. Sizzle) there are a couple of different approaches to getting them recognized in the system. The universal goal is to make them as quick as possible across all browsers (especially on the slower ones like IE).
So in my case, I needed to write a filter to determine which of the elements I searched for are actually visible on screen at the moment the filter runs. This requires some constant querying against the window size and other dimensions in each loop iteration.
This is ok, but can be slow depending on how many different values are needlessly rebuilt on each pass. Of course I could use a closure to store those values but that is not always convenient and needs to be done in every instance.
Having had to work with Sizzle without jQuery in the past I knew there was an alternative to do what I wanted. In effect, I need some code that executes on the first pass and again on the final pass. jQuery provides a couple of ways to create and execute a filter. The first won't do at all, which is:
jQuery.filter( function(index) {...} );
The reason this doesn't work is that we only insight as to which index where working with, not the total number of values possible. Also, it't another layer of function calling indirection and on very DOM heavy pages this turns out to be problematic and slow.
The following syntax is much closer to what we want and I believe is just another way of writing what I ended up using below. I *think* that adding the elements parameter on the end would actually provide access to the array of elements from which the currently tested element comes from but this assumption is based on the parameters being positionally the same as in the function I used. I haven't tested it. Also, I think it's harder to read with the weird [':'] accessor.
jQuery.expr[':'].onscreen = function(elem, pos, match/*, elements */)
The following function actually patches into Sizzle so I know it's leaner (at least when compared to the first approach described above). The key bits here are pos and elements. With these parameters I can have a bit of code in the beginning of the loop that sets up the reused overly calculated values (such as window dimensions) and some code on that executes after the last iteration to remove it.
One of the reasons this format isn't advertised as much as it could be is because the elements array must not be modified and there is no near zero performance solution to make that value read only or harmless if modified.
jQuery.expr.filters.onscreen = function( elem, pos, match, elements )
The way this is done is something akin to:
function (elem, pos, match, elements) {
if (pos == 0) {
// do setup code here
}
// do normal looping here
if (pos == elements.length - 1) {
// do tear down code here
}
}
The only thing missing here is data storage and possibly a more elegant way to invoke the setup and tear down. Using this approach cut my query time down from 170-200ms per call to about 60-70ms per call. This is huge and also what caused me to share my concerns and suggestions. For my solution, I ended up using the window as a place to store my data but this is not exactly ideal. I was careful to do a delete on added properties but a less global place would be more ideal.
My suggestion is that something along the lines of
jQuery.addFilter(loop(elem, pos, match, elements, shared) {...}, setup(elements, match, shared), teardown(elements, match, shared));
Where loop, setup and teardown are functions (named explicitly for readability). The setup and teardown functions are provided the list of elements to be processed and the regex match (in case arguments to the filter need to be accounted for i.e. :onscreen(debug, everyOther) or any other combination that makes sense) and also a shared data object.
This shared data object is an empty, non-null, object literal that is persisted through the execution of the filter. Data can be read and written to during setup, each iteration pass and teardown. After teardown is executed it is deleted and out of scope.
This would have jQuery do something along the lines of the following whenever a filter is executed
- Construct the normal elements specified in the selector or jQuery initialization as per usual
- Create a new object literal and call the setup function (if supplied) passing in the elements, regex match and shared data object
- Loop over each element in the list passing in all five values; elem, pos, match, elements, shared. Making note to only keep the elements that return true from this function call
- Call the teardown function (if supplied) passing in the elements, regex match and shared data object
- Delete the shared data object
- Return the matched elements (those that return true in step 3)
This would give almost everybody the full capabilities they need to write lean, mean filters for jQuery without sacrificing performance or capability.
Thoughts?