Learning JavaScript

This page lists some common misconceptions and solutions to improve JavaScript performance or proper code in general. The intention is to speed up our code base and to make sure code does what it intends. For example  returns true, for starters.

Selector performance (jQuery)
Just like the browser's stylesheet engine that parses native CSS, jQuery's selector engine (Sizzle) selects from right to left. Let's understand what this means for the following selector which may look fast / specific but in fact is not.

This particular example may be extreme, but consider the individual tips and tricks.

The above example will initiate the Sizzle engine, starting by getting all Anchor tags on the entire page (there's quite a few! Over 1300 on an average edit page on a Wikimedia wiki, don't forget about hidden links and anchors for javascript actions). After getting all those it will check these one by one to see if their parentNode is a ListItem. If it's not it'll get the parentNode of the parentNode and so on, until it's all the way to the document.body and gives up and get's started on the next anchor tag. The anchor tags in the Sidebar and in the Personal-area will match for a while for ListItem and UnorderedList and eventually they are dropped when it comes to the  (since the sidebar and personal-area are not descendants of #bodyContent). After that it'll still continue for the few anchor tags left in the match, eventhough there is only one #bodyContent it'll do one last check in the selector since  was part of the selector as well.

Sizzle has a number of optimization shortcuts for re-rooting queries in certain known forms, and on browsers that support querySelectorAll in many cases (such as this one) the entire query is handed off to native browser code bypassing the Sizzle engine entirely.

So how to make this more efficient ? The answer is. Context means to get elements from within the context of one or more elements, rather than from the entire. In plain javascript this is how that goes: So the example given is much faster like this due to context find:

You can optimize even further (depending on your scenario) like this:
 * Dropped the  selector.
 * Removed  from the selector. Unless you expect to have invalid ListItems without a list around them or if you specifically need to filter out OrderedLists, there's no need to specify each element in the tree.
 * Added a direct-child expression to speed up the filtering of anchor tags. Ofcourse if your situation may contain  then you don't want this. But whenever you can, use the direct child expression.

Generally one could describe writing more effecient selectors as "Be specific in what you want, and do not baby the selector engine". Specify what you really want, don't add in more crap if that's not what you need (ie. stuff that just happens to be the case like the extra "div" in front of the ID and the "ul" in between. Unless, as noted, if you specifically need the "div" or "ul" in which case it's fine to add them in the selector).

Null or undefined
There are more than a dozen ways of comparing or looking at a variable to determine whether it is null or undefined (or either of them). Look at the following for actual performance numbers and decide for yourself what gives the best performance for your scenario:
 * http://jsperf.com/testing-undefined
 * http://jsperf.com/testing-null

The fastest way to check if an (un)passed argument or object property is undefined is by strictly comparing it to the  literal.
 * Argument or object property undefined

So why is a typeof comparison slow ? The reason is that there are a few things going on. For one, the typeof operator returns a string which is a fairly expensive operation. So what really happens is an operation followed by a string comparison. Check the jsperf's above to compare results.
 * What about typeof ?

So why do people use ? It's often used to avoid throwing a ReferenceError if the variable was undeclared. However in pretty much every scenario the variable isn't undeclared, just undefined. The two can be confusing, but once you know it will make your code cleaner, simpler and faster.
 * Variable undeclared

Arguments and object properties are never considered undeclared. The only scenario in which referring to a variable throws an exception is when the variable is undeclared (in addition to being undefined). And the only way that can happen is by referring to a variable that has no "var" declaration in the local scope, and isn't a global variable. Which should be rare in most cases, and in good quality code not happen at all. See the following scenarios

Comparing to  is no different from any other comparison and results in the same:

iAmUndeclared was either intended to be a local variable not declared through, or a global variable that for some reason isn't available. Either way a bad situation that can be prevented:


 * Local variable
 * Global variable

There is one more common scenario I'd like to touch here and that's variable declared/defined conditionally. This is a bad habit, because it's confusing. Consider the following construction: It looks like one has to use  here because   could be undeclared, since the   is inside the condition. So would this really be a situation in which we need  ? No. Using strict comparison instead of  would actually work just fine in the above example. This is because " " statements, in JavaScript, are "hoisted" internally to the top of the current scope (regardless of whether it's declared conditionally). More about hoisting of variables and functions: nettuts+, adequatelygood.com.

As a good practice, it is recommended to combine all your var statements at the beginning of a scope. Our example would then look like this:

Lastly, to put these cases in a real-world scenario, let's provide default values / fallbacks for variables, in a way that doesn't need a typeof check either. Or even, if your argument won't / shouldn't be false-y:
 * Fallback

Checking if a variable is one of 'null' or 'undefined' (like  in PHP) can be done comparing to both.
 * Null OR undefined

Array detection
Arrays do not have a  "array". Nor do they validate as "instanceof Array" (atleast not consistently and cross-browser).

As of JavaScript 1.8.5, the array object constructor has an native object method (not a prototype) called "isArray". It can be used like this:

However this is new and not cross-browser yet. The other reliable way to do this to call " " and verify that it is equal to " ". However this is slightly slower.

jQuery has had a built-in object method called "isArray" that does the latter isString comparison that works cross-browser. As of jQuery 1.4 (up to atleast 1.6.1) it only does that if the, faster, native Array.isArray isn't available. The best of both.

So the safest and fastest way to check if a variable is an array is to use.

String concatenation
As in a number of languages, concatenating an array of strings together at once is often faster than concatenating them one at a time, as it avoids creating a lot of intermediate string objects that will never get used again.

In inner loops this sometimes makes a significant time difference -- usually it will be far too small to measure for any one-off operation.


 * http://jsperf.com/build-or-regex-array-vs-string

WikiEditor
Some stuff Catrope discovered when optimizing EditToolbar. These may not be universally true, so when in doubt run both alternatives through the Firebug profiler or use http://jsperf.com


 * Building simple HTML that doesn't need stuff like .data or .click yourself and feeding it to .html or .append is literally about 100 times as fast (YMMV) as building it with jQuery
 * Even if you do need .data or .click, something like this is still faster (at least for 20-200 children, YMMV):
 * Avoid setting .data on hundreds of elements that have a common parent or near ancestor. Instead, add an empty object to the parent's .data and fill that through a reference using the child's rel property as keys. This reduces the number of .data calls a lot.
 * .text is faster than .html if you're only adding text
 * .addClass('foo') is faster than .attr('class', 'foo')

jQuery.size vs .length property
As one can see in the jQuery source, the size function returns the  property of the jQuery object.

As there's no functional difference nor length in number of characters (" " vs. " ") and only a small performance penalty, its use is discouraged.

UI responsiveness
Try to keep heavy calculations/string processing, document reflows, and other slow manipulation to a minimum; keeping responsiveness good is important to keep the web site from feeling slow for users.

If you have to perform a lot of activity in response to some user action, consider dividing the processing into short chunks that can be split over timeouts or intervals, allowing UI events to be processed while you work. For some tasks, HTML 5 Web Workers may be appropriate, but we don't yet have good infrastructure for integrating these with ResourceLoader.

Startup
Initial page load is often the slowest part of things because we have to wait for a lot of resources to get loaded, be parsed by the browser, and get put together.

JavaScript that doesn't need to run at startup should consider delaying operations to avoid slowing things down.

In particular, try to avoid delaying loading by forcing a synchronous script load near the top of the page! This can actually delay seeing the entire page, which is very sad.

Animation
For a lot of common things like fade-in, fade-out, slide-up/down etc there are common jQuery functions set up to handle these for you.

In MediaWiki 1.18 and higher (jQuery 1.6 and higher) these will automatically make use of the requestAnimationFrame interface on supporting browsers, which can help the animations to run smoothly and use less CPU time.

It may also in some cases be more efficient to use CSS 3 transitions; however we don't use a lot that likely benefits from these yet.

Memory usage and leaks
Most of MediaWiki operates on a traditional page navigation/form-submission model; this means that you'll have the entire HTML & JavaScript state potentially wiped clean and rebuilt each time the user jumps from one page to another.

However, be aware that pages may be open for some time in a user's browser during editing, reading, etc -- or left open overnight. In 2011-2012 we can expect more ongoing interactions and real-time updates to happen without actually switching pages, and so being aware of what gets held in memory and how long may become more important.

In particular, some things to watch out for:
 * callbacks bound to HTML element events will stick around along with the elements -- including any variables and functions referenced from its closure context! In some cases you may want to discard an element or unbind a specific event handler after done with it.
 * accidental globals stick around until the page closes; don't forget to use your 'var's