September 12, 2009
Recent Changes tojQuery’s InternalsJohn Resig
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.
✤ Built for selectors that people actually use.Selector % Used # of Uses#id 51.290% 1431.class 13.082% 365tag 6.416% 179tag.class 3.978% 111#id tag 2.151% 60tag#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% 22tag[name=value] 0.645% 18.class tag 0.573% 16[name=value] 0.538% 15tag 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
{ "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, "maxTime":1.991, "fileName":"jquery.js (line 2059)" }, // etc.]}
fireunit.getProfile();
http://ejohn.org/blog/function-call-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-profiling/
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)