JavaScript’s var statement declares and optionally initializes one or more variables in the scope of the current function (or as global variables when used outside a function). Since var accepts multiple declarations, separated by commas, there’s usually no reason to use it more than once per function; it’s just a waste of bytes and—especially if you redeclare variables inside a loop—CPU cycles.
Overuse of var statements is one of the most common problems I see in JavaScript code. I was guilty of it myself for quite a while and it took me a long time to break the habit.
Bad:
function getElementsByClassName(className, tagName, root) {
var elements = [];
var root = root || document;
var tagName = tagName || '*';
var haystack = root.getElementsByTagName(tagName);
var regex = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)');
for (var i = 0, length = haystack.length; i < length; ++i) {
var el = haystack[i];
if (el.className && regex.test(el.className)) {
elements.push(el);
}
}
return elements;
}
There are several things wrong with the example above.
The most obvious problem is that I’ve used the var statement no less than seven times. Somewhat less obvious, but far worse: I’ve used it inside a loop, which means that I’m unnecessarily redeclaring a variable on each iteration. I’ve also unnecessarily redeclared two variables that were passed in as function arguments.
Naturally, there’s a much better way to do this.
Good:
function getElementsByClassName(className, tagName, root) {
root = root || document;
tagName = tagName || '*';
var elements = [],
haystack = root.getElementsByTagName(tagName),
length = haystack.length,
regex = new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)'),
el, i;
for (i = 0; i < length; ++i) {
el = haystack[i];
if (el.className && regex.test(el.className)) {
elements.push(el);
}
}
return elements;
}
There are circumstances in which it is actually necessary to redeclare a variable within a single scope, but they’re very rare, and are more often than not a warning sign that you need to rethink the code you’re writing.
Comments
YUI Compressor
The YUI Compressor, used with the -v [—verbose] option will let you know about the obvious case of having several ‘var’ statements within a single function scope.
Note that having multiple ‘var’ statements within a single function scope (in your example, in the body of a ‘for’ loop) won’t make you waste CPU cycles once the code has been compiled to byte code (thank God!)
Re: YUI Compressor
Yup, YUI Compressor’s
-voption was instrumental in helping me break this habit. Your complaints about my use ofvarinside loops helped too. ;)You're such a noob
For:
for (i = 0; i < length; +i) {
How about:
for (i = 0; length[i]; +i) {
I just saved you one byte – it’s this kind of peer (LOL) code review that I bring to the table.
Also, why not a regular expression literal? Your regexp is a constant, does new RegExp() offer anything other than possibly making the code more readable, i.e., color coding to make it stand out? Isn’t new RegExp() sort of like doing new Array() vs [] ?
Re: You're such a noob
I know it seems clever, but
haystack[i](which I assume is what you meant) is more expensive thani < length. A regexp literal can’t be used here since regexp literals can’t contain variables and can’t be concatenated.Oh boy, there is serious egg on my face...
This is why I’m the Juku Retard…It looks like you’re using non-capturing groups for your beginning/end of line/whitespace regexp. What’s the advantage of that? Is this the same as checking for an optional word boundary at the beginning/end? Something I suppose like:
var retard = new RegExp(‘^\\w?’ + className + ‘\\w?$’);
Re: Oh boy, there is serious egg on my face...
Bah, everyone learns by making mistakes. Especially me.
Using non-capturing groups when you don’t actually need to capture anything results in a slightly more efficient regexp, although I imagine the performance gain in a simple case like this one is pretty insignificant. It’s a good habit to get into, though.
The regexp
'^\\w?' + className + '\\w?$'would match a single class name that may or may not be preceded or followed by alphanumeric characters (but not spaces). So a search for the classfoowould result in matches forfooas well as bad matches forfoobarandbarfoobar, but would not match when an element has multiple class names, such asfoo bar.By matching a class name preceded and followed either by whitespace or by the beginning or end of the string, we ensure that we’ll match only the exact class name we’re looking for, and that we’ll find our class even if it’s one of multiple space-delimited class names.
Oops, I did it again
Oops, I have word character when it should be word boundary. Would this work? Optional word boundary at beginning/end?
var retard = new RegExp(‘^\\b?’ + className + ‘\\b?$’);
Re: Oops, I did it again
Ah, I see what you’re getting at. You’re close, but you need to remove the anchors and the quantifier:
var retard = new RegExp('\\b' + className + '\\b');Unfortunately, although this looks like it’ll work, it’s flawed.
Certain non-whitespace characters, like hyphens, are considered word boundaries, and this could result in unwanted matches since a lot of these characters are valid within class names. That’s why we need to explicitly match either whitespace or a string boundary and nothing else.
Totally Agree!
Hey Ryan,
Awesome post. It’s actually given me some new insight into how I should structure my own code. I think it makes the code so much easier to read/maintain.
Looking forward to more posts like this!
Check the specs
ECMA 262 10.1.3 defines JS’s behavior as regards variable instantiation.
Variable declaration order is immaterial, as is the use of the list-style declarations vs. var-per-variable. All declared variables are added to the execution context’s variable object, in declaration order, when the scope is entered. Not during execution of the statements in the scope, but before then. So var in a loop is the same as var outside of a loop, which is actually the same as var at the very end of your function.
(function (){
var foo = 1;
var bar = 2;
return foo + bar;
})();
is equivalent to
(function (){
var foo, bar;
foo = 1;
bar = 2;
return foo + bar;
})();
which is equivalent to
(function (){
foo = 1;
bar = 2;
return foo + bar;
var foo, bar;
})();
which is, of course, considerably different from
(function (){
foo = 1;
bar = 2;
return foo + bar;
})();
While your argument regarding code size holds, you should of course minify your code if you care about size, which should take care of multi-var-to-list declaration conversions. The code itself should be written for readability and maintainability, not download size.
In functions of any length, I find that having the declaration near the point of first use is helpful. Though if you’re going for obscure, you could put all your variable declarations in an unreachable conditional block, or at the end of the function.
Re: Check the specs
I’m not intimately familiar with the ECMAScript spec, but after reading that section, I agree that you may be right that execution time isn’t affected by multiple declarations (assuming, of course, that the various browser implementations actually adhere to this part of the spec). I’ll do some benchmarks and post the results.
That said, I’m not aware of any JavaScript minification tool that converts multiple explicit variable declarations scattered throughout a function into a single list-style declaration at the beginning of the function. That kind of optimization seems very likely to introduce unexpected errors, which is probably why it isn’t done.
I do find it more readable to declare variables near the point of first use, but writing compact JavaScript requires certain compromises, and consolidating your variable declarations is an easy way to save bytes while sacrificing only a minimum of readability.
Re: Check the specs
I ran some benchmarks in which I performed 300,000 iterations of each of the functions given in the article. Here are the results:
These differences are ridiculously small given the huge number of iterations, so I’m willing to concede the point that multiple variable declarations don’t seem to have any significant effect on execution performance.
A needle in your niple.
I do not usually practice masochism while programming javascript (IE is enough for me), and I respect what other people do. But this doesn’t mean that I agree.
The example is quite misleading. You say it’s bad because it doesn’t apply a rule you (or more people) want to impose in your code: it’s bad because you forgot the needle. Remember not to explain a word using the word you’re trying to explain. (to eat: when you eat something).
The 2cents for “No Biscuit”. I can say it louder but not much more clear. Variables are declared when you declare the scope, not when you execute it. This also helps to understand how closures work in javascript.
My “masochism” point is because “I believe” that some times more than one var can help to understand (read) code better.
But it’s your post, your code policy, so you can call me ugly and stupid® Linus Torvalds.
:)