Vous êtes sur la page 1sur 51

Recent Changes to

jQuery’s Internals
John Resig

September 12, 2009


No More Browser Sniffing


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

✤ div.innerHTML = ' <link/><table></table><a href="/a"


style="color:red;float:left;opacity:.5;">a
</a><select><option>text</option></select>';
jQuery.support
✤ jQuery.support = {

! ! // IE strips leading whitespace when .innerHTML is used


! ! leadingWhitespace: div.firstChild.nodeType == 3,

! ! // Make sure that tbody elements aren't automatically inserted


! ! // IE will insert them into empty tables
! ! tbody: !div.getElementsByTagName("tbody").length,

! ! // Make sure that link elements get serialized correctly by innerHTML


! ! // This requires a wrapper element in IE
! ! htmlSerialize: !!div.getElementsByTagName("link").length,

! ! // Get the style information from getAttribute


! ! // (IE uses .cssText insted)
! ! style: /red/.test( a.getAttribute("style") ),

! ! // Make sure that URLs aren't manipulated


! ! // (IE normalizes it by default)
! ! hrefNormalized: a.getAttribute("href") === "/a",

! ! // Make sure that element opacity exists


! ! // (IE uses filter instead)
! ! opacity: a.style.opacity === "0.5",

! ! // Verify style float existence


! ! // (IE uses styleFloat instead of cssFloat)
! ! cssFloat: !!a.style.cssFloat,

! ! // Will be defined later


! ! scriptEval: false,
! ! noCloneEvent: true,
! ! boxModel: null
! };
Modularity
✤ Starting in 1.3.3 code is broken up even more

✤ Core has been split into many sub-modules

✤ 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

.css("color", "red"); 495 5n

.css({color: "red", border: "1px solid red"}); 887 9n

.remove(); 23772 2n+n2

.append("<p>test</p>"); 307 3n
Complexity Analysis


Reducing call count helps to reduce complexity


Results for 1.3.3:

Method Calls Big-O

.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

String: typeof object === "string"


• Number: typeof object === "number"
• Boolean: typeof object === "boolean"
• Object: typeof object === "object"
• Function: jQuery.isFunction(object)
• Array: jQuery.isArray(object)
• Element: object.nodeType
• null: object === null
• undefined: typeof variable === "undefined" or object.prop === undefined
• null or undefined: object == null
isFunction / isArray


isFunction overhauled in 1.3, isArray added in 1.3.3

toString = Object.prototype.toString

isFunction: function( obj ) {

! ! return toString.call(obj) === "[object Function]";


! },

! isArray: function( obj ) {


! ! return toString.call(obj) === "[object Array]";
! },
Core
.selector / .context


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

! var width = elem.offsetWidth, height = elem.offsetHeight,


! ! force = /^tr$/i.test( elem.nodeName ); // ticket #4512
! return ( width === 0 && height === 0 && !force ) ?
! ! true :
! ! ! ( width !== 0 && height !== 0 && !force ) ?
! ! ! ! false :
! ! ! ! ! !!( jQuery.curCSS(elem, "display") === "none" );
};
ID-rooted Queries


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

! ! return this.remove( selector, true );


! },
DOM Traversal
.closest()


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

! ! var ret = this.pushStack( "", "find", selector ), length = 0;

! ! for ( var i = 0, l = this.length; i < l; i++ ) {


! ! ! length = ret.length;
! ! ! jQuery.find( selector, this[i], ret );

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

.addClass("test"); 592 O(6n)

.removeClass("test"); 754 O(8n)

.removeClass("test"); 610 O(6n)

.css("color", "red"); 495 O(5n)

.css({color: "red", border: "1px solid red"}); 887 O(9n)

.remove(); 23772 O(2n+n2)

.append("<p>test</p>"); 307 O(3n)

.append("<p>test</p><p>test</p><p>test</p><p>test</p><p>test</p>"); 319 O(3n)

.show(); 394 O(4n)

.hide(); 394 O(4n)

.html("<p>test</p>"); 28759 O(3n+n2)

.empty(); 28452 O(3n+n2)

.is("div"); 110

.filter("div"); 216 O(2n)

.find("div"); 1564 O(16n)


Method Calls O(N)
.addClass("test"); 2

.addClass("test"); 2

.removeClass("test"); 2

.removeClass("test"); 2

.css("color", "red"); 102

.css({color: "red", border: "1px solid red"}); 201 O(2n)

.remove(); 299 O(3n)

.append("<p>test</p>"); 116

.append("<p>test</p><p>test</p><p>test</p><p>test</p><p>test</p>"); 128

.show(); 394 O(4n)

.hide(); 394 O(4n)

.html("<p>test</p>"); 100

.empty(); 201 O(2n)

.is("div"); 109

.filter("div"); 216 O(2n)

.find("div"); 495 O(5n)

Vous aimerez peut-être aussi