How to Write Better Javascript
Most web projects involve writing Javascript, these days it’s more popular than ever.
It’s important to know the fundamentals of how to write good Javascript, even if you don’t become a JS Ninja. There are some simple guidelines you can follow to instantly upgrade your skills, and ensure clean, maintainable code.
Before we dive into that, let’s look at some beginner mistakes.
What Not To Do
I’ve seen some horrible Javascript, I mean the type of thing you can’t un-see.
I once saw a file with dozens of jQuery ready functions, one after another:
jQuery(document).ready(function($) { $('.button').click(function() { alert('do something'); }); }); jQuery(document).ready(function($) { $('.another-button').click(function() { alert('do something else'); }); }); jQuery(document).ready(function($) { $('#another-button2').click(function() { alert('do something else'); }); }); jQuery(document).ready(function($) { $('#another-button3').click(function() { alert('do something else'); }); }); ...ad infinitum
This is way too much repeated code, and will quickly become difficult to read and maintain. You should only have the document ready function in one place, to initialize your code. We’ll talk more about that in a bit.
(Also, the .ready() function has been deprecated, so you shouldn’t use it anymore)
Another thing I’ve seen is the dreaded nested if statements:
if( $variable = condition ) { // do something if( $variable2 = condition ) { // do something if( $variable3 = condition ) { // do something if( $variable4 = condition ) { // do something if( $variable5 = condition ) { // do something } } } } }
It’s really hard to tell what the conditions are for executing code inside the nested if statements, and it can quickly become overcomplicated. If your code looks like this, you may need to think through how you can simplify.
Another thing we don’t want is jQuery soup. This is where we have a bunch of jQuery functions with no particular organization.
$(function() { $('.click').click(function(){ // do something }); $('.header').css({'color': 'red'}); $('#coffee').hover( makeMeCoffee ); });
This may work if you are only writing 3 functions, but projects always grow beyond that. No one wants to sort through a 600 line file of jQuery soup. We’ll look at better structure for your JS file later in this article.
There are a lot of bad practices out there, and we can’t cover them all, but here are some more things to avoid.
// Not using a closure means everything is a global, this is bad! var isGlobal = true; // this will show up as window.isGlobal, polluting the global space // Instead, use an anonymous function like below (function() { // not using the var keyword makes a global myVar = 'bad'; var myVar = 'good'; // not caching variables document.getElementById('my-btn').style.color = 'red'; document.getElementById('my-btn').innerHTML = 'Click me'; // better var myBtn = document.getElementById('my-btn'); myBtn.style.color = 'red'; myBtn.innerHTML = 'Click me'; })();
Avoid globals and always cache your elements. Looking for an element every time you run a function uses a lot of memory, plus it’s just unnecessary.
Now that we have that out of the way, let’s look at some good examples.
Best Practices
We’ve looked at some of the things we shouldn’t do, so let’s look at best practices.
Here’s a boilerplate of how I like to start my script files:
(function(window, document, $, undefined){ 'use strict'; window.myPrefix = {}; myPrefix.init = function() { // get it started } myPrefix.init(); })(window, document, jQuery);
First of all, we are using a self executing anonymous function. It sounds really fancy, but it’s just an enclosure to make sure we aren’t polluting the global space. We saw above how defining variables outside of an enclosure makes them available in the window object, which can cause collisions and generally makes things messy.
You can put ‘use strict’ inside your closure to get better error messages when you do something you aren’t supposed to. For example:
"use strict"; x = 3.14; // This will cause an error because we didn't use the var keyword
The next thing we do is create a global variable that will hold all of our variables and functions: window.myPrefix = {}. You should prefix this since it will be in the global space, but anything inside of it does not need a prefix. This is similar to using classes in PHP or ES6.
Everything will be contained inside of window.myPrefix, which means we are only adding one thing to the global namespace.
Next we kick things off with an init function: myPrefix.init(). Everything we do inside of our closure needs myPrefix.whatever to ensure it’s all going into our global variable.
Other developers can access myPrefix.init() from their Javascript if they want to manipulate our code. If we just use an anonymous function, other developers can’t access our code.
$(function() { function init() { // other developers can't access this } init(); });
Writing our code with an enclosure with a prefixed global variable allows us to keep it organized and accessible by other developers.
Here’s an example of caching some selectors, and adding another function.
(function(window, document, $, undefined){ 'use strict'; window.myPrefix = {}; myPrefix.init = function() { // developers can access this myPrefix.cacheSelectors(); myPrefix.doSomething(); } myPrefix.cacheSelectors = function() { // cache our selectors here, can use them throughout the file myPrefix.body = $('body'); myPrefix.classes = 'class1 class2'; } myPrefix.doSomething = function() { // example of using our cached selectors myPrefix.body.addClass( myPrefix.classes ); } myPrefix.init(); })(window, document, jQuery);
Now we have a nice structure for our file, avoiding globals and caching selectors, and it’s accessible by other developers.
Since we are using jQuery, this file must be enqueued after the jQuery library. Using WordPress, we do this by adding jquery as a dependency for our script like this:
add_action( 'wp_enqueue_scripts', 'prefix_do_scripts' ); prefix_do_scripts() { wp_enqueue_script( 'prefix-js', plugin_dir_url( __FILE__ ) . 'assets/js/prefix-script.js', array( 'jquery' ),'1.0', true ); }
Random Tips
Here are some common issues that we didn’t cover above.
Don’t do this:
$('.btn').click( function() { // do something }); $('.element').hover( function() { // do something }); $('#target').focus( function() { // do something });
Instead, you can chain your events by using .on() (formerly .bind). Also, cache and abstract your functions like below.
// cache all the things var body = $('body'); var myHandler = function( e ) { e.preventDefault(); // do something } body // start chaining events .on( 'click', '.btn', myHandler ) .on( 'hover', '.element', myHandler ) .on( 'focus', '#target', anotherHandler );
By chaining our events with .on() and abstracting our callbacks, we make this much easier to read. We can also add all of our events in one place for easier reference, and make sure everything is cached properly.
CSS in our JS
Anything related to CSS or animation should always be handled by adding and removing classes. For example, we don’t want to use:
$( '.btn' ).css( "color", "red" );
Instead, we can do:
$( '.btn' ).addClass( 'red' );
And in our style.css file we have:
.red { color: red }
The same applies for hiding and showing elements, and doing animations. You should never use jQuery slideToggle() or animate(), instead add and remove classes that use CSS animation.
If you follow these guidelines you’ll be well on your way to writing clean, maintainable Javascript. Shout out to Justin Sternberg who taught me a lot of this stuff, also if you want to get more advanced check out this post on Angular 2, Typescript, and Ionic 2.
I’m sure there are lots of best practices I missed, let me know your tips in the comments.
* Updated 9/18 to remove jQuery.ready (deprecated)
Great post Scott. I don’t really have sound knowledge on JavaScript, but I have been reading tutorials just like this. I hope to become perfect in coding JavaScript. Thanks for sharing.
Awesome!
[…] Write Better JavaScript […]
Nice post Scott,
How should one write common utility methods which can be called from any other JS, this is one of many problems we face as Rails developer leading to writing all common functions in “common.js” 🙁
Hi Bhavesh, you’ll notice in the anonymous function enclosure I write a single window variable like window.myPrefix. The functions inside the enclosure are accessible through this window variable, for example window.myPrefix.init()
Keeping everything inside of a single global variable prevents us from polluting the global space.
Hey i was unable to run my methods and then if i put () before the myPrefix.init it was working right.
so my code was exactly same but I only changed this line ‘$(document).on( ‘ready’, myPrefix.init );’
to ‘$(document).on(‘ready’, myPrefix.init());’
can u explain it pls…
$(document).on( "ready", handler )
has been removed in jQuery 3.0, you should just call myPrefix.init() by itself at the end of your anonymous function instead. I’ve updated the post above.Thanks for the post. I have such a hard time taking what I’ve learned about core JS and turning it into a good, readable code. It’s easy to do small with js, but once the complexity goes up, it just becomes difficult for me to grasp how to write it all out in a proper way. I found this post helpful in moving in that direction.