Writing performant code is absolutely critical. Poorly written JavaScript can significantly slow down and even crash the browser. On mobile devices, it can prematurely drain batteries and contribute to data overages. Performance at the browser level is a major part of user experience.
JavaScript libraries should only be loaded on the page when needed. jquery-1.11.1.min.js
is 96 KB. This isn’t a huge deal on desktop but can add up quickly on mobile when we start adding a bunch of libraries. Loading a large number of libraries also increases the chance of conflicts.
jQuery is a JavaScript framework that allows us easily accomplish complex tasks such as AJAX and animations. jQuery is great for certain situations but overkill for others.
If you only need to perform simple tasks, there might be a good alternative in raw JavaScript. A very helpful compilation of these cases is available on You Might Not Need jQuery.
It’s a common JavaScript mistake to reselect something unnecessarily. For example, every time a menu button is clicked, we do not need to reselect the menu. Rather, we select the menu once and cache its selector. This applies whether you are using jQuery or not. For example:
non-jQuery Uncached:
var hideButton = document.getElementsByClassName( 'hide-button' )[0];
hideButton.onclick = function() {
var menu = document.getElementById( 'menu' );
menu.style.display = 'none';
}
non-jQuery Cached:
var menu = document.getElementById( 'menu' );
var hideButton = document.getElementsByClassName( 'hide-button' )[0];
hideButton.onclick = function() {
menu.style.display = 'none';
}
jQuery Uncached:
var $hideButton = jQuery( '.hide-button' );
$hideButton.on( 'click', function() {
var $menu = jQuery( '#menu' );
$menu.hide();
});
jQuery Cached:
var $menu = jQuery( '#menu' );
var $hideButton = jQuery( '.hide-button' );
$hideButton.on( 'click', function() {
$menu.hide();
});
Notice how in cached versions we are pulling the menu selection out of the event handler so it only happens once. Non-jQuery cached is not surprisingly the fastest way to handle this situation.
Event delegation is the act of adding one event listener to a parent node to listen for events bubbling up from children. This is much more performant than adding one event listener for each child element. Here is an example:
Without jQuery:
document.getElementById( 'menu' ).addEventListener( 'click', function( event ) {
if( event.target && event.target.nodeName === 'LI' ) {
// Do stuff!
}
});
With jQuery:
jQuery( '#menu' ).on( 'click', 'li', function() {
// Do stuff!
});
The non-jQuery method is as usual more performant. You may be wondering why we don’t just add one listener to <body>
for all our events. Well, we want the event to bubble up the DOM as little as possible for performance reasons. This would also be pretty messy code to write.
Standardizing the way we structure our JavaScript allows us to collaborate more effectively with one another. Using intelligent design patterns improves maintainability, code readability, and even helps to prevent bugs.
Adding methods or properties to the window
object or the global namespace should be done carefully. window
object pollution can result in collisions with other scripts. We should wrap our scripts in closures and expose methods and properties to window
decisively.
When a script is not wrapped in a closure, the current context or this
is actually window
:
window.console.log( this === window ); // true
for ( var i = 0; i < 9; i++ ) {
// Do stuff
}
var result = true;
window.console.log( window.result === result ); // true
window.console.log( window.i === i ); // true
When we put our code inside a closure, our variables are private to that closure unless we expose them:
( function() {
for ( var i = 0; i < 9; i++ ) {
// Do stuff
}
window.result = true;
})();
window.console.log( typeof window.result !== 'undefined' ); // true
window.console.log( typeof window.i !== 'undefined' ); // false
Notice how i
was not exposed to the window
object.
A common pattern for encapsulating some JS functionality is:
/* global _fooExports */
/* exported Foo */
var Foo = (function( $ ) {
var self = {
bar: 1,
baz: 2
};
if ( 'undefined' !== typeof _fooExports ) {
$.extend( self, _fooExports );
}
self.init = function( config ) {
$.extend( self, config || {} );
/* ... */
};
// Bootstrap when DOM ready.
$( function() {
self.init();
} );
return self;
})( jQuery );
The _fooExports
is a variable which is exported from PHP by wp_localize_script()
(or the like).
Note that it is better if the bootstrap is not done here but rather is decoupled by Foo.init()
being called elsewhere.
It’s important we use language features that are intended to be used. This means not using deprecated functions, methods, or properties. Whether we are using a JavaScript or a library such as jQuery or Underscore, we should not use deprecated features. Using deprecated features can have negative effects on performance, security, maintainability, and compatibility.
For example, in jQuery jQuery.live()
is a deprecated method:
jQuery( '.menu' ).live( 'click', function() {
// Clicked!
});
We should use jQuery.on()
instead:
jQuery( '.menu' ).on( 'click', function() {
// Clicked!
});
Another example in JavaScript is escape()
and unescape()
. These functions were deprecated. Instead we should use encodeURI()
, encodeURIComponent()
, decodeURI()
, and decodeURIComponent()
.
We conform to WordPress JavaScript coding standards.
We conform to the WordPress JavaScript Documentation Standards.
Use ESLint to automate checks for coding standards violations. Include the .eslintrc
from project-gulp-boilerplate into your theme/plugin/site repo to configure these linters.
With the influx of JavaScript upgrades in recent years, the need for a third-party library to polyfill functionality is becoming more and more rare (outside of a build script). Don’t load in extensions unless the benefit outweighs the size of and added load-time of using it. While it is often more efficient for coding to use a quick jQuery method, it is rarely worth bringing in an entire library for one-off instances.
If you are working on a legacy project that already contains a library, make sure you’re still evaluating the need for it as you build out features to best set up clients for the future.
There are many JavaScript libraries available today. Many of them directly compete with each other. We try to stay consistent with what WordPress uses.
Underscore - Provides a number of useful utility functions such as clone()
, each()
, and extend()
. WordPress core uses this library quite a bit.
ReactJS - Using React provides a library to create large-scale, stateful JavaScript applications. It aims to provide a flexible system of creating highly componentized user interfaces.
Vue - Implementing Vue on a project allows us to take advantage of the statefulness built into something like React, but apply it on a much more lightweight and smaller scale as to not bog down performance by loading in a heavy library.