From b096d431eadb87e307a2f633a27809dafd6d9fa6 Mon Sep 17 00:00:00 2001 From: Dan Heberden Date: Thu, 17 Mar 2011 11:27:22 -0700 Subject: [PATCH] Replace ch09 with original ch09 ocntent --- .../ch09-performance-bp.html | 842 +++++++----------- 1 file changed, 328 insertions(+), 514 deletions(-) diff --git a/book/part3-advanced-topics/ch09-performance-bp.html b/book/part3-advanced-topics/ch09-performance-bp.html index 53e513af..d9ef5ee4 100644 --- a/book/part3-advanced-topics/ch09-performance-bp.html +++ b/book/part3-advanced-topics/ch09-performance-bp.html @@ -1,619 +1,433 @@ -
+

- Code Organization + Performance Best Practices

-
-

- Overview -

+

+ This chapter covers a number of jQuery and JavaScript best practices, in no particular order. Many of the best practices in this chapter are based on the jQuery Anti-Patterns for Performance presentation by Paul Irish. +

+
+
+
+
+

+ Cache length during loops +

+
+
+

- When you move beyond adding simple enhancements to your website with jQuery and start developing full-blown client-side applications, you need to consider how to organize your code. In this chapter, we'll take a look at various code organization patterns you can use in your jQuery application and explore the RequireJS dependency management and build system. + In a for loop, don't access the length property of an array every time; cache it beforehand.

-
-
-

- Key Concepts -

+
+var myLength = myArray.length;
+
+for (var i = 0; i < myLength; i++) {
+    // do stuff
+}
+
+
+
+
+
+
+

+ Append new content outside of a loop +

+
-

- Before we jump into code organization patterns, it's important to understand some concepts that are common to all good code organization patterns. -

-
    -
  • Your code should be divided into units of functionality — modules, services, etc. Avoid the temptation to have all of your code in one huge $(document).ready() block. This concept, loosely, is known as encapsulation. -
  • -
  • -

    - Don't repeat yourself. Identify similarities among pieces of functionality, and use inheritance techniques to avoid repetitive code. -

    -
  • -
  • -

    - Despite jQuery's DOM-centric nature, JavaScript applications are not all about the DOM. Remember that not all pieces of functionality need to — or should — have a DOM representation. -

    -
  • -
  • -

    - Units of functionality should be loosely coupled — a unit of functionality should be able to exist on its own, and communication between units should be handled via a messaging system such as custom events or pub/sub. Stay away from direct communication between units of functionality whenever possible. -

    -
  • -
-

- The concept of loose coupling can be especially troublesome to developers making their first foray into complex applications, so be mindful of this as you're getting started. -

-
-
-

- Encapsulation -

- The first step to code organization is separating pieces of your application into distinct pieces; sometimes, even just this effort is sufficient to lend + Touching the DOM comes at a cost; if you're adding a lot of elements to the DOM, do it all at once, not one at a time.

-
-
-

- The Object Literal -

-
-

- An object literal is perhaps the simplest way to encapsulate related code. It doesn't offer any privacy for properties or methods, but it's useful for eliminating anonymous functions from your code, centralizing configuration options, and easing the path to reuse and refactoring. -

-
-

- An object literal -

-
-
-var myFeature = {
-    myProperty : 'hello',
+            
+// this is bad
+$.each(myArray, function(i, item) {
+   var newListItem = '<li>' + item + '</li>';
+   $('#ballers').append(newListItem);
+});
 
-    myMethod : function() {
-        console.log(myFeature.myProperty);
-    },
+// better: do this
+var frag = document.createDocumentFragment();
 
-    init : function(settings) {
-        myFeature.settings = settings;
-    },
+$.each(myArray, function(i, item) {
+    var newListItem = '<li>' + item + '</li>';
+    frag.appendChild(newListItem);
+});
+$('#ballers')[0].appendChild(frag);
 
-    readSettings : function() {
-        console.log(myFeature.settings);
-    }
-};
+// or do this
+var myHtml = '';
 
-myFeature.myProperty; // 'hello'
-myFeature.myMethod(); // logs 'hello'
-myFeature.init({ foo : 'bar' });
-myFeature.readSettings(); // logs { foo : 'bar' }
+$.each(myArray, function(i, item) {
+    html += '<li>' + item + '</li>';
+});
+$('#ballers').html(myHtml);
 
+
+
+
+
+
+

+ Keep things DRY +

-

- The object literal above is simply an object assigned to a variable. The object has one property and several methods. All of the properties and methods are public, so any part of your application can see the properties and call methods on the object. While there is an init method, there's nothing requiring that it be called before the object is functional. -

-

- How would we apply this pattern to jQuery code? Let's say that we had this code written in the traditional jQuery style: -

-
-// clicking on a list item loads some content
-// using the list item's ID and hides content
-// in sibling list items
-$(document).ready(function() {
-  $('#myFeature li')
-    .append('<div/>')
-    .click(function() {
-      var $this = $(this);
-      var $div = $this.find('div');
-      $div.load('foo.php?item=' +
-        $this.attr('id'),
-        function() {
-          $div.show();
-          $this.siblings()
-            .find('div').hide();
-        }
-      );
-    });
-});
-
-

- If this were the extent of our application, leaving it as-is would be fine. On the other hand, if this was a piece of a larger application, we'd do well to keep this functionality separate from unrelated functionality. We might also want to move the URL out of the code and into a configuration area. Finally, we might want to break up the chain to make it easier to modify pieces of the functionality later. -

-
-

- Using an object literal for a jQuery feature -

-
-
-var myFeature = {
-    init : function(settings) {
-        myFeature.config = {
-            $items : $('#myFeature li'),
-            $container : $('<div class="container"></div>'),
-            urlBase : '/foo.php?item='
-        };
-
-        // allow overriding the default config
-        $.extend(myFeature.config, settings);
-
-        myFeature.setup();
-    },
+            
+

+ Don't repeat yourself; if you're repeating yourself, you're doing it wrong. +

+
+// BAD
+if ($eventfade.data('currently') != 'showing') {
+    $eventfade.stop();
+}
 
-    setup : function() {
-        myFeature.config.$items
-            .each(myFeature.createContainer)
-            .click(myFeature.showItem);
-    },
+if ($eventhover.data('currently') != 'showing') {
+    $eventhover.stop();
+}
 
-    createContainer : function() {
-        var $i = $(this),
-            $c = myFeature.config.$container.clone()
-                     .appendTo($i);
+if ($spans.data('currently') != 'showing') {
+    $spans.stop();
+}
 
-        $i.data('container', $c);
-    },
+// GOOD!!
+var $elems = [$eventfade, $eventhover, $spans];
+$.each($elems, function(i,elem) {
+    if (elem.data('currently') != 'showing') {
+        elem.stop();
+    }
+});
+
+
+
+
+
+
+

+ Beware anonymous functions +

+
+
+
+

+ Anonymous functions bound everywhere are a pain. They're difficult to debug, maintain, test, or reuse. Instead, use an object literal to organize and name your handlers and callbacks. +

+
+// BAD
+$(document).ready(function() {
+    $('#magic').click(function(e) {
+        $('#yayeffects').slideUp(function() {
+            // ...
+        });
+    });
 
-    buildUrl : function() {
-        return myFeature.config.urlBase +
-               myFeature.$currentItem.attr('id');
-    },
+    $('#happiness').load(url + ' #unicorns', function() {
+        // ...
+    });
+});
 
-    showItem : function() {
-        var myFeature.$currentItem = $(this);
-        myFeature.getContent(myFeature.showContent);
+// BETTER
+var PI = {
+    onReady : function() {
+        $('#magic').click(PI.candyMtn);
+        $('#happiness').load(PI.url + ' #unicorns', PI.unicornCb);
     },
 
-    getContent : function(callback) {
-        var url = myFeature.buildUrl();
-        myFeature.$currentItem
-            .data('container').load(url, callback);
+    candyMtn : function(e) {
+        $('#yayeffects').slideUp(PI.slideCb);
     },
 
-    showContent : function() {
-        myFeature.$currentItem
-            .data('container').show();
-        myFeature.hideContent();
-    },
+    slideCb : function() { ... },
 
-    hideContent : function() {
-        myFeature.$currentItem.siblings()
-            .each(function() {
-                $(this).data('container').hide();
-            });
-    }
+    unicornCb : function() { ... }
 };
 
-$(document).ready(myFeature.init);
+$(document).ready(PI.onReady);
 
+
+
+
+
+
+

+ Optimize Selectors +

+
+

+ Selector optimization is less important than it used to be, as more browsers implement document.querySelectorAll() and the burden of selection shifts from jQuery to the browser. However, there are still some tips to keep in mind. +

+
+
+

+ ID-Based Selectors +

+

- The first thing you'll notice is that this approach is obviously far longer than the original — again, if this were the extent of our application, using an object literal would likely be overkill. Assuming it's not the extent of our application, though, we've gained several things: + Beginning your selector with an ID is always best.

-
    -
  • -

    - We've broken our feature up into tiny methods. In the future, if we want to change how content is shown, it's clear where to change it. In the original code, this step is much harder to locate. -

    -
  • -
  • -

    - We've eliminated the use of anonymous functions. -

    -
  • -
  • -

    - We've moved configuration options out of the body of the code and put them in a central location. -

    -
  • -
  • -

    - We've eliminated the constraints of the chain, making the code easier to refactor, remix, and rearrange. -

    -
  • -
+
+// fast
+$('#container div.robotarm');
+
+// super-fast
+$('#container').find('div.robotarm');
+

- For non-trivial features, object literals are a clear improvement over a long stretch of code stuffed in a $(document).ready() block, as they get us thinking about the pieces of our functionality. However, they aren't a whole lot more advanced than simply having a bunch of function declarations inside of that $(document).ready() block. + The $.fn.find approach is faster because the first selection is handled without going through the Sizzle selector engine — ID-only selections are handled using document.getElementById(), which is extremely fast because it is native to the browser.

-
+

- The Module Pattern + Specificity

- The module pattern overcomes some of the limitations of the object literal, offering privacy for variables and functions while exposing a public API if desired. + Be specific on the right-hand side of your selector, and less specific on the left.

-
-

- The module pattern -

-
-
-var feature =(function() {
-
-    // private variables and functions
-    var privateThing = 'secret',
-        publicThing = 'not secret',
-
-        changePrivateThing = function() {
-            privateThing = 'super secret';
-        },
-
-        sayPrivateThing = function() {
-            console.log(privateThing);
-            changePrivateThing();
-        };
-
-    // public API
-    return {
-        publicThing : publicThing,
-        sayPrivateThing : sayPrivateThing
-    }
-
-})();
-
-feature.publicThing; // 'not secret'
+              
+// unoptimized
+$('div.data .gonzalez');
 
-feature.sayPrivateThing();
-// logs 'secret' and changes the value
-// of privateThing
+// optimized
+$('.data td.gonzalez');
 
-
-

- In the example above, we self-execute an anonymous function that returns an object. Inside of the function, we define some variables. Because the variables are defined inside of the function, we don't have access to them outside of the function unless we put them in the return object. This means that no code outside of the function has access to the privateThing variable or to the changePrivateThing function. However, sayPrivateThing does have access to privateThing and changePrivateThing, because both were defined in the same scope as sayPrivateThing. + Use tag.class if possible on your right-most selector, and just tag or just .class on the left.

- This pattern is powerful because, as you can gather from the variable names, it can give you private variables and functions while exposing a limited API consisting of the returned object's properties and methods. + Avoid excessive specificity.

+
+$('.data table.attendees td.gonzalez');
+
+// better: drop the middle if possible
+$('.data td.gonzalez');
+

- Below is a revised version of the previous example, showing how we could create the same feature using the module pattern while only exposing one public method of the module, showItemByIndex(). + A "flatter" DOM also helps improve selector performance, as the selector engine has fewer layers to traverse when looking for an element.

-
-

- Using the module pattern for a jQuery feature -

-
-
-$(document).ready(function() {
-    var feature = (function() {
-
-        var $items = $('#myFeature li'),
-            $container = $('<div class="container"></div>'),
-            $currentItem,
-
-            urlBase = '/foo.php?item=',
-
-            createContainer = function() {
-                var $i = $(this),
-                    $c = $container.clone().appendTo($i);
-
-                $i.data('container', $c);
-            },
-
-            buildUrl = function() {
-                return urlBase + $currentItem.attr('id');
-            },
-
-            showItem = function() {
-                var $currentItem = $(this);
-                getContent(showContent);
-            },
-
-            showItemByIndex = function(idx) {
-                $.proxy(showItem, $items.get(idx));
-            },
-
-            getContent = function(callback) {
-                $currentItem.data('container').load(buildUrl(), callback);
-            },
-
-            showContent = function() {
-                $currentItem.data('container').show();
-                hideContent();
-            },
-
-            hideContent = function() {
-                $currentItem.siblings()
-                    .each(function() {
-                        $(this).data('container').hide();
-                });
-            };
-
-        $items
-            .each(createContainer)
-            .click(showItem);
-
-        return { showItemByIndex : showItemByIndex };
-    })();
+            
+
+
+

+ Avoid the Universal Selector +

+
+

+ Selections that specify or imply that a match could be found anywhere can be very slow. +

+
+$('.buttons > *');  // extremely expensive
+$('.buttons').children();  // much better
 
-    feature.showItemByIndex(0);
-});
+$('.gender :radio');  // implied universal selection
+$('.gender *:radio'); // same thing, explicit now
+$('.gender input:radio'); // much better
 
-
-
-

- Managing Dependencies -

-
-

- Note -

-

- This section is based heavily on the excellent RequireJS documentation at http://requirejs.org/docs/jquery.html, and is used with the permission of RequireJS author James Burke. -

-
-

- When a project reaches a certain size, managing the script modules for a project starts to get tricky. You need to be sure to sequence the scripts in the right order, and you need to start seriously thinking about combining scripts together into a bundle for deployment, so that only one or a very small number of requests are made to load the scripts. You may also want to load code on the fly, after page load. -

-

- RequireJS, a dependency management tool by James Burke, can help you manage the script modules, load them in the right order, and make it easy to combine the scripts later via the RequireJS optimization tool without needing to change your markup. It also gives you an easy way to load scripts after the page has loaded, allowing you to spread out the download size over time. -

-

- RequireJS has a module system that lets you define well-scoped modules, but you do not have to follow that system to get the benefits of dependency management and build-time optimizations. Over time, if you start to create more modular code that needs to be reused in a few places, the module format for RequireJS makes it easy to write encapsulated code that can be loaded on the fly. It can grow with you, particularly if you want to incorporate internationalization (i18n) string bundles, to localize your project for different languages, or load some HTML strings and make sure those strings are available before executing code, or even use JSONP services as dependencies. -

-
+
-

- Getting RequireJS -

+
+
+

+ Use Event Delegation +

+
+

- The easiest way to use RequireJS with jQuery is to download a build of jQuery that has RequireJS built in. This build excludes portions of RequireJS that duplicate jQuery functionality. You may also find it useful to download a sample jQuery project that uses RequireJS. + Event delegation allows you to bind an event handler to one container element (for example, an unordered list) instead of multiple contained elements (for example, list items). jQuery makes this easy with $.fn.live and $.fn.delegate. Where possible, you should use $.fn.delegate instead of $.fn.live, as it eliminates the need for an unnecessary selection, and its explicit context (vs. $.fn.live's context of document) reduces overhead by approximately 80%.

-
-
-
-

- Using RequireJS with jQuery -

-

- Using RequireJS in your page is simple: just include the jQuery that has RequireJS built in, then require your application files. The following example assumes that the jQuery build, and your other scripts, are all in a scripts/ directory. + In addition to performance benefits, event delegation also allows you to add new contained elements to your page without having to re-bind the event handlers for them as they're added.

-
-

- Using RequireJS: A simple example -

-
-
-<!DOCTYPE html>
-<html>
-    <head>
-        <title>jQuery+RequireJS Sample Page</title>
-        <script src="scripts/require-jquery.js"></script>
-        <script>require(["app"]);</script>
-    </head>
-    <body>
-        <h1>jQuery+RequireJS Sample Page</h1>
-    </body>
-</html>
+            
+// bad (if there are lots of list items)
+$('li.trigger').click(handlerFn);
+
+// better: event delegation with $.fn.live
+$('li.trigger').live('click', handlerFn);
+
+// best: event delegation with $.fn.delegate
+// allows you to specify a context easily
+$('#myList').delegate('li.trigger', 'click', handlerFn);
 
+
+
+
+
+
+

+ Detach Elements to Work With Them +

+

- The call to require(["app"]) tells RequireJS to load the scripts/app.js file. RequireJS will load any dependency that is passed to require() without a .js extension from the same directory as require-jquery.js, though this can be configured to behave differently. If you feel more comfortable specifying the whole path, you can also do the following: + The DOM is slow; you want to avoid manipulating it as much as possible. jQuery introduced $.fn.detach in version 1.4 to help address this issue, allowing you to remove an element from the DOM while you work with it.

-<script>require(["scripts/app.js"]);</script>
-
-

- What is in app.js? Another call to require.js to load all the scripts you need and any init work you want to do for the page. This example app.js script loads two plugins, jquery.alpha.js and jquery.beta.js (not the names of real plugins, just an example). The plugins should be in the same directory as require-jquery.js: -

-
-

- A simple JavaScript file with dependencies -

-
-
-require(["jquery.alpha", "jquery.beta"], function() {
-    //the jquery.alpha.js and jquery.beta.js plugins have been loaded.
-    $(function() {
-        $('body').alpha().beta();
-    });
-});
+var $table = $('#myTable');
+var $parent = $table.parent();
+
+$table.detach();
+// ... add lots and lots of rows to table
+$parent.append(table);
 
-
-
-
+
-

- Creating Reusable Modules with RequireJS -

+
+
+

+ Use Stylesheets for Changing CSS on Many Elements +

+
+

- RequireJS makes it easy to define reusable modules via require.def(). A RequireJS module can have dependencies that can be used to define a module, and a RequireJS module can return a value — an object, a function, whatever — that can then be consumed by yet other modules. -

-

- If your module does not have any dependencies, then just specify the name of the module as the first argument to require.def(). The second argument is just an object literal that defines the module's properties. For example: + If you're changing the CSS of more than 20 elements using $.fn.css, consider adding a style tag to the page instead for a nearly 60% increase in speed.

-
-

- Defining a RequireJS module that has no dependencies -

-
-
-require.def("my/simpleshirt",
-    {
-        color: "black",
-        size: "unisize"
-    }
-);
-
-
-
-

- This example would be stored in a my/simpleshirt.js file. -

-

- If your module has dependencies, you can specify the dependencies as the second argument to require.def() (as an array) and then pass a function as the third argument. The function will be called to define the module once all dependencies have loaded. The function receives the values returned by the dependencies as its arguments (in the same order they were required in the array), and the function should return an object that defines the module. -

-
-

- Defining a RequireJS module with dependencies -

-
-
-require.def("my/shirt",
-    ["my/cart", "my/inventory"],
-    function(cart, inventory) {
-        //return an object to define the "my/shirt" module.
-        return {
-            color: "blue",
-            size: "large"
-            addToCart: function() {
-                inventory.decrement(this);
-                cart.add(this);
-            }
-        }
-    }
-);
-
-
-
-

- In this example, a my/shirt module is created. It depends on my/cart and my/inventory. On disk, the files are structured like this: -

-
-my/cart.js
-my/inventory.js
-my/shirt.js
-
-

- The function that defines my/shirt is not called until the my/cart and my/inventory modules have been loaded, and the function receives the modules as the cart and inventory arguments. The order of the function arguments must match the order in which the dependencies were required in the dependencies array. The object returned by the function call defines the my/shirt module. Be defining modules in this way, my/shirt does not exist as a global object. Modules that define globals are explicitly discouraged, so multiple versions of a module can exist in a page at a time. -

-

- Modules do not have to return objects; any valid return value from a function is allowed. -

-
-

- Defining a RequireJS module that returns a function -

-
-
-require.def("my/title",
-    ["my/dependency1", "my/dependency2"],
-    function(dep1, dep2) {
-        //return a function to define "my/title". It gets or sets
-        //the window title.
-        return function(title) {
-            return title ? (window.title = title) : window.title;
-        }
-    }
-);
+            
+// fine for up to 20 elements, slow after that
+$('a.swedberg').css('color', '#asd123');
+$('<style type="text/css">a.swedberg { color : #asd123 }</style>')
+    .appendTo('head');
 
-
+
+
+
+
+
+

+ Use $.data Instead of $.fn.data +

-

- Only one module should be required per JavaScript file. -

+

+ Using $.data on a DOM element instead of calling $.fn.data on a jQuery selection can be up to 10 times faster. Be sure you understand the difference between a DOM element and a jQuery selection before doing this, though. +

+
+// regular
+$(elem).data(key,value);


+
+// 10x faster
+
$.data(elem,key,value);
+
-
+
-

- Optimizing Your Code: The RequireJS Build Tool -

+
+
+

+ Don't Act on Absent Elements +

+
+

- Once you incorporate RequireJS for dependency management, your page is set up to be optimized very easily. Download the RequireJS source and place it anywhere you like, preferrably somewhere outside your web development area. For the purposes of this example, the RequireJS source is placed as a sibling to the webapp directory, which contains the HTML page and the scripts directory with all the scripts. Complete directory structure: + jQuery won't tell you if you're trying to run a whole lot of code on an empty selection — it will proceed as though nothing's wrong. It's up to you to verify that your selection contains some elements.

-requirejs/ (used for the build tools)
-webapp/app.html
-webapp/scripts/app.js
-webapp/scripts/require-jquery.js
-webapp/scripts/jquery.alpha.js
-webapp/scripts/jquery.beta.js
+// BAD: this runs three functions
+// before it realizes there's nothing
+// in the selection
+$('#nosuchthing').slideUp();
+
+// Better
+var $mySelection = $('#nosuchthing');
+if ($mySelection.length) { $mySelection.slideUp(); }
+
+// BEST: add a doOnce plugin
+jQuery.fn.doOnce = function(func){
+    this.length && func.apply(this);
+    return this;
+
}
+
+
$('li.cartitems').doOnce(function(){

+    // make it ajax! \o/

+});
 

- Then, in the scripts directory that has require-jquery.js and app.js, create a file called app.build.js with the following contents: + This guidance is especially applicable for jQuery UI widgets, which have a lot of overhead even when the selection doesn't contain elements.

-
-

- A RequireJS build configuration file -

-
-
-{
-    appDir: "../",
-    baseUrl: "scripts/",
-    dir: "../../webapp-build",
-    //Comment out the optimize line if you want
-    //the code minified by Closure Compiler using
-    //the "simple" optimizations mode
-    optimize: "none",
-
-    modules: [
-        {
-            name: "app"
-        }
-    ]
-}
-
+
+
+
+
+
+

+ Variable Definition +

+

- To use the build tool, you need Java 6 installed. Closure Compiler is used for the JavaScript minification step (if optimize: "none" is commented out), and it requires Java 6. -

-

- To start the build, go to the webapp/scripts directory, execute the following command: + Variables can be defined in one statement instead of several.

-# non-windows systems
-../../requirejs/build/build.sh app.build.js
-
-# windows systems
-..\..\requirejs\build\build.bat app.build.js
+// old & busted
+var test = 1;
+var test2 = function() { ... };
+var test3 = test2(test);
+
+// new hotness
+var test = 1,
+    test2 = function() { ... },
+    test3 = test2(test);
 

- Now, in the webapp-build directory, app.js will have the app.js contents, jquery.alpha.js and jquery.beta.js inlined. If you then load the app.html file in the webapp-build directory, you should not see any network requests for jquery.alpha.js and jquery.beta.js. + In self-executing functions, variable definition can be skipped all together.

+
+(function(foo, bar) { ... })(1, 2);
+
-
-

- Exercises -

-
-
-

- Create a Portlet Module -

+
+
+
+
+

+ Conditionals +

+
-

- Open the file /exercises/portlets.html in your browser. Use the file /exercises/js/portlets.js. Your task is to create a portlet creation function that uses the module pattern, such that the following code will work: -

-
-var myPortlet = Portlet({
-    title : 'Curry',
-    source : 'data/html/curry.html',
-    initialState : 'open' // or 'closed'
-});
+            
+
+// old way
+if (type == 'foo' || type == 'bar') { ... }
 
-myPortlet.$element.appendTo('body');
-
-

- Each portlet should be a div with a title, a content area, a button to open/close the portlet, a button to remove the portlet, and a button to refresh the portlet. The portlet returned by the Portlet function should have the following public API: -

-
-myPortlet.open(); // force open state
-myPortlet.close(); // force close state
-myPortlet.toggle(); // toggle open/close state
-myPortlet.refresh(); // refresh the content
-myPortlet.destroy(); // remove the portlet from the page
-myPortlet.setSource('data/html/onions.html');
-// change the source
+// better
+if (/^(foo|bar)$/.test(type)) { ... }
+
+// object literal lookup
+if (({ foo : 1, bar : 1 })[type]) { ... }
 
+
+
+
+
+
+

+ Don't Treat jQuery as a Black Box +

+
+
+

+ Use the source as your documentation — bookmark http://bit.ly/jqsource and refer to it often. +

\ No newline at end of file