Recent Changes to jQuery's Internals

51
September 12, 2009 Recent Changes to jQuery’s Internals John Resig

description

A run-down of all the features and internal changes made in 1.3, 1.3.1, 1.3.2, and 1.3.3/1.4.

Transcript of Recent Changes to jQuery's Internals

Page 1: Recent Changes to jQuery's Internals

September 12, 2009

Recent Changes tojQuery’s InternalsJohn Resig

Page 2: Recent Changes to jQuery's Internals

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.

Page 3: Recent Changes to jQuery's Internals

jQuery.support

✤ div.innerHTML = ' <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a </a><select><option>text</option></select>';

Page 4: Recent Changes to jQuery's Internals

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! };

Page 5: Recent Changes to jQuery's Internals

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

Page 6: Recent Changes to jQuery's Internals

Modularity

✤ Starting to reduce dependencies in-between files

✤ Makes it easier to dynamically load portions of the library

✤ (Mobile jQuery!)

Page 7: Recent Changes to jQuery's Internals

Mobile jQuery?

✤ “Heavy” core.js

✤ ready event

✤ find() (powered by querySelectorAll)

✤ filter() (very basic functionality)

✤ $.getScript() (for loading modules)

✤ Still experimenting...

Page 8: Recent Changes to jQuery's Internals

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

Page 9: Recent Changes to jQuery's Internals

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.

Page 10: Recent Changes to jQuery's Internals

Reducing Complexity

Page 11: Recent Changes to jQuery's Internals

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();

Page 12: Recent Changes to jQuery's Internals

Stack Profiling

✤ jQuery Stack Profiler

✤ Look for problematic methods and plugins

✤ http://ejohn.org/blog/deep-profiling-jquery-apps/

Page 13: Recent Changes to jQuery's Internals
Page 14: Recent Changes to jQuery's Internals

FireUnit

✤ A simple JavaScript test suite embedded in Firebug.

✤ http://fireunit.org/

Page 15: Recent Changes to jQuery's Internals

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/

Page 16: Recent Changes to jQuery's Internals

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

Page 17: Recent Changes to jQuery's Internals

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/

Page 18: Recent Changes to jQuery's Internals

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.

Page 19: Recent Changes to jQuery's Internals

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

Page 20: Recent Changes to jQuery's Internals

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

Page 21: Recent Changes to jQuery's Internals

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]";! },

Page 22: Recent Changes to jQuery's Internals

Core

Page 23: Recent Changes to jQuery's Internals

.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” );};

Page 24: Recent Changes to jQuery's Internals

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

Page 25: Recent Changes to jQuery's Internals

jQuery(“TAG”)

✤ In 1.3.3

✤ jQuery(“body”) is now a shortcut for jQuery(document.body)

✤ jQuery(“TAG”) is optimized (skips Sizzle)

Page 26: Recent Changes to jQuery's Internals

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

Page 27: Recent Changes to jQuery's Internals

.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 }

Page 28: Recent Changes to jQuery's Internals

Selectors

Page 29: Recent Changes to jQuery's Internals

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(“*”))

Page 30: Recent Changes to jQuery's Internals

: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" ); };

Page 31: Recent Changes to jQuery's Internals

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.

Page 32: Recent Changes to jQuery's Internals

DOM Manipulation

Page 33: Recent Changes to jQuery's Internals

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

Page 34: Recent Changes to jQuery's Internals

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;

Page 35: Recent Changes to jQuery's Internals

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>”);}

Page 36: Recent Changes to jQuery's Internals

$(“<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!)

Page 37: Recent Changes to jQuery's Internals

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;});

Page 38: Recent Changes to jQuery's Internals

.detach()

✤ In 1.3.3

✤ Like .remove() but leaves events and data intact.

✤ detach: function( selector ) {

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

Page 39: Recent Changes to jQuery's Internals

DOM Traversal

Page 40: Recent Changes to jQuery's Internals

.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.

Page 41: Recent Changes to jQuery's Internals

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;! },

Page 42: Recent Changes to jQuery's Internals

find() perf

✤ Perf for $(“...”) improved, as well (hooked into rootQuery, uses less function calls)

Page 43: Recent Changes to jQuery's Internals

.not() / .filter()

✤ .not(function(){}) (1.3.3)

✤ .filter(Element), .filter(function(){}) (1.3.3)

✤ Full API parity inbetween .not() and .filter()

Page 44: Recent Changes to jQuery's Internals

.index()

✤ In 1.3.3

✤ $(“div”).index() - position of element relative to siblings

✤ $(“#foo”).index(“div”) - position relative to all divs

Page 45: Recent Changes to jQuery's Internals

Events

Page 46: Recent Changes to jQuery's Internals

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

Page 47: Recent Changes to jQuery's Internals

.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 );

Page 48: Recent Changes to jQuery's Internals

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.

Page 49: Recent Changes to jQuery's Internals

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)

Page 50: Recent Changes to jQuery's Internals

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)

Page 51: Recent Changes to jQuery's Internals