Real-World JavaScript Anti-Patterns (Part Two)
My previous post was mainly about pure JavaScript syntax, callbacks and iterating collections. In this post I would like to focus on usage of a couple of popular third-party libraries: jQuery and Lodash. As in part one, I will use present JavaScript snippets encountered during real code reviews and suggest how they could be improved. I'm bending the rules a bit and will also present some of my favorite lesser known features of these libraries, even if it isn't strictly an anti-pattern not to use them.
jQuery
Let’s start with good old jQuery. Although most web developers are well-acquainted with this library, you can still find code like the following:
if (limited) {
$('body').addClass('height-limited');
} else {
$('body').removeClass('height-limited');
}
The authors of jQuery knew about this common case and they wrote the if
statement for us. So we can change this to a one-liner:
$('body').toggleClass('height-limited', limited);
Now consider a similar pattern with unnecessary condition check (not related exclusively to jQuery). Imagine that we need to handle focus changes of a specific element:
function handler(e) {
if (e.type == 'blur') { … }
if (e.type == 'focus') { … }
}
$(el).blur(handler);
$(el).focus(handler);
Clearly it would be better to have separate handlers for blur
and focus
and get rid of the conditional checks in the handler. You might protest that no one would write something so obviously inelegant, but in reality code along these lines shows up all the time, camouflaged into various more sophisticated forms (e.g. code flow is already branched by some condition, but later all the branches leads to single “bottle-neck” function). Keep your handlers small and specific and you won't have to use redundant conditional checks.
Nowadays, with high-level frameworks like React/Ember/Angular, direct class and event manipulation is on its way out. But jQuery is still used this way, whether on standalone sites or in conjunction with another framework (e.g. inside an Angular directive).
For example, we might have a component that needs to run some code on window resize:
$(window).on('resize', resizeHandler);
In a single page application, the component is mounted and unmounted many times over the application lifecycle. If each component initialization attaches the resize handler then we must be sure to detact it when the component is destroyed. It is easy to forget this though, resulting in an ever growing number of obsolete handlers. This in turn leads to memory leaks and performance issues. These mistakes can go unnoticed for a long time since they might be invisible on your mighty 16GB RAM developer machine but cause serious trouble on a normal user’s five year old laptop. (And this is not just speculation as this section was motivated by mainly painful hours tracking down exactly this type of problem.)
We could deal with this by keeping track of every handler we attach in every component, but this is tedious and error-prone. Instead we can use a handy but often neglected JQuery feature: event namespaces.
$(window).on('resize.mycomponent', resizeHandler);
You can specify a namespace for an event by appending it with a period to the event type when adding it, as in the above example. All namespaced events can then be detached at once:
$(window).off('.mycomponent');
You still need to remember to call off
, but you don’t need to keep track of all the events you have attached. This also makes refactoring safer since we no longer risk changing the attaching code but forgetting to add or update the corresponding detaching code. In fact, if you have a lot of similar components, you can derive a namespace from its name and have the base implementation deal with cleaning up, so the actual components don't have to worry about this at all.
Lodash
Personally I find Lodash to be an essential part of almost every JavaScript project. I see its main purpose as fixing annoying JavaScript quirks (insert your favorite JavaScript bashing joke here) and adding missing collection functions that should really have been present in the language itself (and no doubt, one day, will be).
I am guessing that many developers familiar with Lodash use it solely in this way. This is a pity. Lodash does a great job of making up for JavaScript's lacks, but it also contains many less well-known but equally handy functions.
Everybody knows _.forEach()
, and most developers are familiar with _.values()
, _.groupBy()
or _pluck()
. But do you know about _.debounce()
? I must admit I implemented it myself several times on several projects before noticing it. The same goes for _.memoize()
.
Another functions are utilities that just makes your code a bit nicer to read.
_.isUndefined(x)
is definitely better than typeof(x) !== "undefined"
.
_.isNaN(x)
is clear to everyone, but x != x
(which is the accepted pure JavaScript way to check for NaN
) will blow your mind if you are not familiar with it.
I would rather write _.clone(arr)
then arr.slice()
, even though the latter is idiomatic and experienced JavaScript developers will have no trouble understanding it.
The last function I want to mention is _.template()
Of course, there are zillions of templating libraries, but this one can be used with no additional overhead if you already have Lodash in your project. And for many purposes _.template()
is good enough. You probably won't use it for creating page markup, but in many cases you can write more elegant code by taking advantage of it. For example, you might want to support placeholders in your email sending function:
var order = { id: 5774, title: 'Sweet lemonade' };
var email = new sendgrid.Email();
email.setSubject("Order #{id} - {title}", order);
Simple and straightforward with _.template()
under the hood. And as you see, you can choose your favourite placeholder syntax.
Conclusion
In general, I would encourage you to make the effort to be more familiar with your third-party libraries. especially those with the breadth of a jQuery or Lodash. You don't have to know every function intimately, but you should have at least a shallow knowledge of all they have to offer. Spare some time to read through the documentation, not just look stuff up when you need it. General-purpose libraries like Lodash, with many loosely related functions, may contain something cool you never suspected was there and will save you the effort of writing and testing your own implementation. The best code, after all, is the code you never have to write.
Even if you are familiar with your libraries, it’s not a bad idea to go back and check for new features from time to time. Do you know about $.Callbacks()
in jQuery, for instance? I was surprised when I spotted it recently. Because I learned jQuery many years ago and consider myself an expert, I am prone to disregard new features. I haven’t used $.Callbacks()
yet, but at least I know now that it exists and what it is about.