Académique Documents
Professionnel Documents
Culture Documents
jQuery’s Internals
John Resig
✤
As of jQuery 1.3
✤
jQuery.browser is now deprecated
✤
All uses of jQuery.browser have been removed from core.
✤
jQuery.support replaces it.
jQuery.support
✤ core.js
✤ attributes.js
✤ css.js
✤ manipulation.js
✤ traversing.js
Modularity
✤
Starting to reduce dependencies in-between files
✤
Makes it easier to dynamically load portions of the library
✤
(Mobile jQuery!)
Mobile jQuery?
✤
“Heavy” core.js
✤
ready event
✤
find() (powered by querySelectorAll)
✤
filter() (very basic functionality)
✤
$.getScript() (for loading modules)
✤
Still experimenting...
Sizzle
✤
Standalone selector engine, landed in jQuery 1.3.
Selector % Used # of Uses
✤
Built for selectors that people actually use. #id 51.290% 1431
.class 13.082% 365
tag 6.416% 179
tag.class 3.978% 111
#id tag 2.151% 60
tag#id 1.935% 54
#id:visible 1.577% 44
#id .class 1.434% 40
.class .class 1.183% 33
* 0.968% 27
#id tag.class 0.932% 26
#id:hidden 0.789% 22
tag[name=value] 0.645% 18
.class tag 0.573% 16
[name=value] 0.538% 15
tag tag 0.502% 14
#id #id 0.430% 12
#id tag tag 0.358% 10
Right to Left
✤
Most selector engines (including jQuery, pre-1.3) evaluate a selector
left to right
✤
“#id div” - finds the element by ID “id” then finds all divs inside of it.
✤
Sizzle finds all divs then checks to see if theres an ancestor with an id
of “id”.
✤
How CSS engines work in browsers.
✤
Only one query per selector, rest is filtering.
Reducing Complexity
Analyzing Performance
✤
Optimizing performance is a huge concern: Faster code = happy
users!
✤
Measure execution time
✤
Loop the code a few times
✤
Measure the difference:
✤
(new Date).getTime();
Stack Profiling
✤
jQuery Stack Profiler
✤
Look for problematic methods and plugins
✤
http://ejohn.org/blog/deep-profiling-jquery-apps/
FireUnit
✤
A simple JavaScript test suite embedded in Firebug.
✤
http://fireunit.org/
FireUnit Profile Data
{
fireunit.getProfile(); "time": 8.443,
"calls": 611,
"data":[
{
"name":"makeArray()",
"calls":1,
"percent":23.58,
"ownTime":1.991,
"time":1.991,
"avgTime":1.991,
"minTime":1.991,
http://ejohn.org/blog/function-call- "maxTime":1.991,
"fileName":"jquery.js (line 2059)"
profiling/
Complexity Analysis
✤
Analyze complexity rather than raw time
✤
jQuery Call Count Profiler (uses FireUnit)
Method Calls Big-O
.addClass("test"); 542 6n
.addClass("test"); 592 6n
.removeClass("test"); 754 8n
.removeClass("test"); 610 6n
.append("<p>test</p>"); 307 3n
Complexity Analysis
✤
Reducing call count helps to reduce complexity
✤
Results for 1.3.3:
.remove(); 298 3n
.html("<p>test</p>"); 507 5n
.empty(); 200 2n
http://ejohn.org/blog/function-call-
ECMAScript 5 Support
✤
New in 1.3.3
✤
Removal of arguments.callee
✤
Switching from ‘RegExp to ‘new RegExp’
✤
Using JSON.parse, where available.
✤
Support for json2.js is auto-baked in.
Unified Syntax
✤
Reusable inline functions and RegExp moved outside.
✤
var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
! rleadingWhitespace = /^\s+/,
! rsingleTag = /^<(\w+)\s*\/?>$/,
! rxhtmlTag = /(<(\w+)[^>]*?)\/>/g,
! rselfClosing = /^(?:abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i,
! rinsideTable = /^<(thead|tbody|tfoot|colg|cap)/,
! rtbody = /<tbody/i,
! fcloseTag = function(all, front, tag){
! ! return rselfClosing.test(tag) ?
! ! ! all :
! ! ! front + "></" + tag + ">";
! };
✤
http://docs.jquery.com/JQuery_Core_Style_Guidelines
Type Checks
✤
isFunction overhauled in 1.3, isArray added in 1.3.3
✤
toString = Object.prototype.toString
✤
Added in 1.3
✤
Predominantly used for plugins
✤
var div = jQuery(“div”);
div.selector === “div”;
div.context === document;
✤
jQuery.fn.update = function(){
return this.pushStack( jQuery(this.selector, this.context), “update” );
};
jQuery(...) unified
✤
In 1.3.3
✤
jQuery(null), jQuery(false), jQuery(undefined) => jQuery([])
✤
jQuery() is still the same as jQuery(document) (for now)
✤
BUT jQuery(“div”) points back to a common jQuery(document) root.
✤
jQuery(“div”).prevObject === jQuery()
jQuery(“TAG”)
✤
In 1.3.3
✤
jQuery(“body”) is now a shortcut for jQuery(document.body)
✤
jQuery(“TAG”) is optimized (skips Sizzle)
Position
✤
All in 1.3.3
✤
.get(-N), .eq(-N)
Access elements/jQuery sets using negative indices.
✤
.first(), .last()
Shortcuts for .eq(0) and .eq(-1)
✤
.toArray()
Shortcut for .get()
.data()
✤
In 1.3.3
✤
Calling .data() (no args) returns the full data object for an element
✤
jQuery(“#foo”).data(“a”, 1).data(“b”, 2).data() =>
{ a: 1, b: 2 }
Selectors
Document Order
if ( document.documentElement.compareDocumentPosition ) {
! sortOrder = function( a, b ) {
! ! var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
! ! if ( ret === 0 ) {
! ! ! hasDuplicate = true;
! ! }
! ! return ret;
! };
} else if ( "sourceIndex" in document.documentElement ) {
! sortOrder = function( a, b ) {
! ! var ret = a.sourceIndex - b.sourceIndex;
! ! if ( ret === 0 ) {
! ! ! hasDuplicate = true;
! ! }
! ! return ret;
! };
} else if ( document.createRange ) {
! sortOrder = function( a, b ) {
! ! var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
! ! aRange.selectNode(a);
! ! aRange.collapse(true);
! ! bRange.selectNode(b);
! ! bRange.collapse(true);
! ! var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
! ! if ( ret === 0 ) {
! ! ! hasDuplicate = true;
! ! }
! ! return ret;
!
}
}; ✤
As of 1.3.2 all selectors return in document order
(as if you did getElementsByTagName(“*”))
:hidden / :visible
✤
Changed in 1.3.2, revised in 1.3.3
✤
Changed logic of :hidden/:visible to use .offsetWidth === 0
&& .offsetHeight === 0 trick
✤ Sizzle.selectors.filters.hidden = function(elem){
✤
Changed in 1.3.3
✤
$(“#id div”) sped up dramatically
✤
#id is detected and used as the root of the query
✤
$(“div”, “#id”) --> $(“#id”).find(“div”)
✤
$(“#id div”) can likely never be as fast as $(“#id”).find(“div”)
✤
$(“#id”) is so hyper-optimized, it’s hard to match raw perf.
DOM Manipulation
HTML Fragments
✤
Overhauled in 1.3
✤
.append(“<li>foo</li>”) first converts HTML to a DOM fragment
✤
Fragments act as a loose collection for DOM nodes
✤
Can be used to insert many nodes with a single operation
✤
var fragment = document.createDocumentFragment();
while ( node.firstChild )
fragment.appendChild( node.firstChild );
document.body.appendChild( node );
HTML Fragments (redux.)
✤
In 1.3.3
✤
It turns out that using fragments makes it really easy to cache them,
as well.
✤
Look for common cases that can be cached (simple, small, HTML
strings that are built more than once).
✤
Also optimized $(“<some html>”), cut out some unnecessary
intermediary code.
✤
jQuery.fragments[“<some html>”] = fragment;
HTML Fragments
✤
Surprising change:
✤
for ( var i = 0; i < 250; i++ ) {
$(“<li>some thing</li>”).attr(“id”, “foo” + i).appendTo(“ul”);
}
✤
Now faster than:
✤
for ( var i = 0; i < 250; i++ ) {
$(“ul”).append(“<li id=‘foo” + i + “‘>some thing</li>”);
}
$(“<div/>”)
✤
In 1.3:
✤
$(“<div/>”) was made equivalent to $(document.createElement
(“div”))
✤
In 1.3.3:
✤
Logic for handling createElement moved to jQuery() (performance
improved!)
append(function(){ })
✤
In 1.3.3 append, prepend, before, after, wrap, wrapAll, replace, replaceAll all
take a function as an argument
✤
Compliments .attr(“title”, function(){}) (and CSS)
✤
Makes:
$(“li”).each(function(){
$(this).append(“My id: “ + this.id);
});
✤
Also:
$(“li”).append(function(){
return “My id: “ + this.id;
});
.detach()
✤
In 1.3.3
✤
Like .remove() but leaves events and data intact.
✤ detach: function( selector ) {
✤
Added in 1.3
✤
$(this).closest(“div”) checks if ‘this’ is a div, if not keeps going up the
tree until it finds the closest one.
✤
In 1.3.3
✤
$(this).closest(“.test”, document.body)
Prevent the traversal from going too far up the tree.
find() perf
✤
In 1.3.3
✤
Reduced the number of function calls - Reduced 16 calls per find() to 5.
✤ find: function( selector ) {
! ! ! if ( i > 0 ) {
! ! ! ! // Make sure that the results are unique
! ! ! ! for ( var n = length; n < ret.length; n++ ) {
! ! ! ! ! for ( var r = 0; r < length; r++ ) {
! ! ! ! ! ! if ( ret[r] === ret[n] ) {
! ! ! ! ! ! ! ret.splice(n--, 1);
! ! ! ! ! ! ! break;
! ! ! ! ! ! }
! ! ! ! ! }
! ! ! ! }
! ! ! }
! ! }
! ! return ret;
! },
find() perf
✤
Perf for $(“...”) improved, as well (hooked into rootQuery, uses less
function calls)
.not() / .filter()
✤
.not(function(){}) (1.3.3)
✤
.filter(Element), .filter(function(){}) (1.3.3)
✤
Full API parity inbetween .not() and .filter()
.index()
✤
In 1.3.3
✤
$(“div”).index() - position of element relative to siblings
✤
$(“#foo”).index(“div”) - position relative to all divs
Events
Live Events
✤
Super-efficient event delegation - uses .closest(), introduced in 1.3.
✤
1.3.3 adds context and data object support.
✤
1.3.3 will ship once “submit”, “change”, and “focus/blur” events
work in .live() (in all browsers).
.bind() `thisObject`
✤
In 1.3.3
✤
You can now bind functions enforcing this `this`
✤
var obj = { method: function(){} };
$(“div”).bind( “click”, function(){
this.objProp = true;
}, obj );
Dynamic Ready Event
✤
In 1.3.3
✤
document.readyState is checked to determine if the body is already
loaded - if so, no need to wait for ready event.
Method Calls O(N)
.addClass("test"); 542 O(6n)
.is("div"); 110
.addClass("test"); 2
.removeClass("test"); 2
.removeClass("test"); 2
.append("<p>test</p>"); 116
.append("<p>test</p><p>test</p><p>test</p><p>test</p><p>test</p>"); 128
.html("<p>test</p>"); 100
.is("div"); 109