Chapter 4. Variables, Functions, and Operators
The real guts of any JavaScript program are the functions you write to accomplish tasks. Inside the functions, variables and operators are used to move bits around and make things happen. That’s why, after getting the basic formatting of your JavaScript down, it’s important to decide how to use functions, variables, and operators to reduce complexity and improve readability.
Variable Declarations
Variable declarations are accomplished by using the var
statement.
JavaScript allows the var
statement to
be used multiple times and nearly anywhere within a script. This usage
creates interesting cognitive issues for developers, because all var
statements are hoisted to the top of the
containing function regardless of where they actually occur in the code.
For example:
function doSomething() { var result = 10 + value; var value = 10; return result; }
In this code, it’s perfectly valid for the variable value
to be used before it was declared, though
it will cause result
to have the
special value NaN
. To understand why,
you need to be aware that this code is changed by the JavaScript engine to
this:
function doSomething() { var result; var value; result = 10 + value; value = 10; return result; }
The two var
statements are
hoisted to the top of the function; the initialization happens afterward.
The variable value
has the special
value undefined
when it’s used on line
6, so result
becomes NaN
(not a number). Only after that is value
finally assigned the value of 10.
One area where developers tend to miss variable declaration hoisting
is with for
statements, in
which variables are declared as part of the initialization:
function doSomethingWithItems(items) { for (var i=0, len=items.length; i < len; i++) { doSomething(items[i]); } }
JavaScript up to ECMAScript 5 has no concept of block-level variable declarations, so this code is actually equivalent to the following:
function doSomethingWithItems(items) { var i, len; for (i=0, len=items.length; i < len; i++) { doSomething(items[i]); } }
Variable declaration hoisting means defining a variable anywhere in a function is the same as declaring it at the top of the function. Therefore, a popular style is to have all variables declared at the top of a function instead of scattered throughout. In short, you end up writing code similar to the manner in which the JavaScript engine will interpret it.
My recommendation is to have your local variables defined as the first statements in a function. This approach is recommended in Crockford’s Code Conventions, the SproutCore Style Guide, and the Dojo Style Guide:
function doSomethingWithItems(items) { var i, len; var value = 10; var result = value + 10; for (i=0, len=items.length; i < len; i++) { doSomething(items[i]); } }
Crockford goes on to recommend the use of a single var
statement at the top of functions:
function doSomethingWithItems(items) { var i, len, value = 10, result = value + 10; for (i=0, len=items.length; i < len; i++) { doSomething(items[i]); } }
The Dojo Style Guide allows combining var
statements only when the variables are
related to one another.
My personal preference is to combine all var
statements with one initialized variable per
line. The equals signs should be aligned. For variables that aren’t
initialized, they should appear last, as in the following example:
function doSomethingWithItems(items) { var value = 10, result = value + 10, i, len; for (i=0, len=items.length; i < len; i++) { doSomething(items[i]); } }
At a minimum, I recommend combining var
statements, as doing so makes your code
smaller and therefore faster to download.
Function Declarations
Function declarations, just like variable declarations, are hoisted by JavaScript engines. Therefore, it’s possible to use a function in code before it is declared:
// Bad doSomething(); function doSomething() { alert("Hello world!"); }
This approach works because the JavaScript engine interprets the code as if it were the following:
function doSomething() { alert("Hello world!"); } doSomething();
Due to this behavior, it’s recommended that JavaScript functions always be declared before being used. This design appears in Crockford’s Code Conventions. Crockford also recommends that local functions be placed immediately after variable declarations within a containing function, as in:
function doSomethingWithItems(items) { var i, len, value = 10, result = value + 10; function doSomething(item) { // do something } for (i=0, len=items.length; i < len; i++) { doSomething(items[i]); } }
Both JSLint and JSHint will warn when a function is used before it is declared.
Additionally, function declarations should never appear inside of block statements. For example, this code won’t behave as expected:
// Bad if (condition) { function doSomething() { alert("Hi!"); } } else { function doSomething() { alert("Yo!"); } }
Exactly how this will work from browser to browser will vary. Most
browsers automatically take the second declaration without evaluating
condition
; Firefox evaluates condition
and uses the appropriate function
declaration. This is a gray area in the ECMAScript specification and should thus be avoided.
Function declarations should be used only outside of conditional
statements. This pattern is explicitly forbidden in the Google JavaScript Style Guide.
Function Call Spacing
Almost universally, the recommended style for function calls is to have no space between the function name and the opening parenthesis, which is done to differentiate it from a block statement. For example:
// Good doSomething(item); // Bad: Looks like a block statement doSomething (item); // Block statement for comparison while (item) { // do something }
Crockford’s Code Conventions explicitly calls this out. The Dojo Style Guide, SproutCore Style Guide, and Google JavaScript Style Guide implicitly recommend this style through code examples.
The jQuery Core Style Guide further specifies that an extra space should be included after the opening parenthesis and before the closing parenthesis, such as:
// jQuery-style doSomething( item );
The intent here is to make the arguments easier to read. The jQuery Core Style Guide also lists some exceptions to this style, specifically relating to functions that are passed a single argument that is an object literal, array literal, function expression, or string. So the following examples are all still considered valid:
// jQuery exceptions doSomething(function() {}); doSomething({ item: item }); doSomething([ item ]); doSomething("Hi!");
Generally speaking, styles with more than one exception are not good, because they can be confusing to developers.
Immediate Function Invocation
JavaScript allows you to declare anonymous functions—functions without proper names—and assign those functions to variables or properties. For example:
var doSomething = function() { // function body };
Such anonymous functions can also be immediately invoked to return a value to the variable by including parentheses at the very end:
// Bad var value = function() { // function body return { message: "Hi" } }();
In the previous example, value
ends up being assigned an object, because the function is immediately
invoked. The problem with this pattern is that it looks very similar to
assigning an anonymous function to a variable. You don’t know that this
isn’t the case until you get to the very last line and see the
parentheses. This sort of confusion hinders the readability of your
code.
To make it obvious that immediate function invocation is taking place, put parentheses around the function, as in this example:
// Good var value = (function() { // function body return { message: "Hi" } }());
This code now has a signal on the first line, the open paren, that the function is immediately invoked. Adding the parentheses doesn’t change the behavior of the code at all. Crockford’s Code Conventions recommends this pattern, and JSLint will warn when the parentheses are missing.
Strict Mode
ECMAScript 5 introduced strict mode, a way to alter how JavaScript is executed and parsed in the hopes of reducing errors. To put a script into strict mode, use the following pragma:
"use strict";
Although this looks like a string that isn’t assigned to a variable, ECMAScript 5 JavaScript
engines treat this as a command to switch into strict mode. This pragma
is valid both globally as well as locally, inside of a single function.
However, it’s a common recommendation (though undocumented in any
popular style guide) to avoid placing "use
strict"
in the global scope. The reason is that strict mode
applies to all code in a single file, so if you’re concatenating 11
files and one of them has global strict mode enabled, all of the files
are placed into strict mode. Because strict mode operates under slightly
different rules than nonstrict mode, there’s a high likelihood of errors
within the other files. For this reason, it’s best to avoid placing
"use strict"
in the global scope.
Here are some examples:
// Bad - global strict mode "use strict"; function doSomething() { // code } // Good function doSomething() { "use strict"; // code }
If you want strict mode to apply to multiple functions without
needing to write "use strict"
multiple times, use immediate function invocation:
// Good (function() { "use strict"; function doSomething() { // code } function doSomethingElse() { // code } })();
In this example, doSomething()
and doSomethingElse()
both run in
strict mode, because they are contained in an immediately invoked
function with "use strict"
specified.
Both JSLint and JSHint warn when "use
strict"
is found outside of a function. Both also expect all
functions to have "use strict"
specified by default; this can be turned off in both tools. I recommend
using strict mode wherever possible to limit common mistakes.
Equality
Equality in JavaScript is tricky due to type coercion. Type coercion causes variables of a specific type to be converted automatically into a different type for a particular operation to succeed, which can lead to some unexpected results.
One of the main areas in which type coercion occurs is with the use
of equality operators, ==
and !=
. These two operators cause type coercion when the two values
being compared are not the same data type (when they are the same data
type, no coercion occurs). There are many instances in which code may not
be doing what you expect.
If you compare a number to a string, the string is first converted to a number, and then the comparison happens. Some examples:
// The number 5 and string 5 console.log(5 == "5"); // true // The number 25 and hexadecimal string 25 console.log(25 == "0x19"); // true
When performing type coercion, the string is converted to a number
as if using the Number()
casting
function. Because Number()
understands
hexadecimal format, it will convert a string that looks like a hexadecimal
number into the decimal equivalent before the comparison occurs.
If a boolean value is compared to a number, then the boolean is
converted to a number before comparison. A false
value becomes 0 and true
becomes 1. For example:
// The number 1 and true console.log(1 == true); // true // The number 0 and false console.log(0 == false); // true // The number 2 and true console.log(2 == true); // false
If one of the values is an object and the other is not, then
the object’s valueOf()
method is called to get a primitive value to compare against. If valueOf()
is not defined, then toString()
is called
instead. After that point, the comparison continues following the
previously discussed rules about mixed type comparisons. For
example:
var object = { toString: function() { return "0x19"; } }; console.log(object == 25); // true
The object is deemed to be equal to the number 25 because its
toString()
method returned the
hexadecimal string "0x19"
, which was
then converted to a number before being compared to 25
.
The last instance of type coercion occurs between null
and undefined
. These two special values are deemed
to be equivalent simply by the letter of
the ECMAScript standard:
console.log(null == undefined); // true
Because of type coercion, avoiding ==
and !=
at
all is recommended; instead, use ===
and !==
. These operators perform comparison without
type coercion. So if two values don’t have the same data type, they are
automatically considered to be unequal, which allows your comparison
statements to always perform the comparison in a way that is more
consistent. Consider the differences between ==
and ===
in
a few cases:
// The number 5 and string 5 console.log(5 == "5"); // true console.log(5 === "5"); // false // The number 25 and hexadecimal string 25 console.log(25 == "0x19"); // true console.log(25 === "0x19"); // false // The number 1 and true console.log(1 == true); // true console.log(1 === true); // false // The number 0 and false console.log(0 == false); // true console.log(0 === false); // false // The number 2 and true console.log(2 == true); // false console.log(2 === true); // false var object = { toString: function() { return "0x19"; } }; // An object and 25 console.log(object == 25); // true console.log(object === 25); // false // Null and undefined console.log(null == undefined); // true console.log(null === undefined);// false
Use of ===
and !==
is recommended by Crockford’s Code Conventions, the jQuery Core Style Guide, and the SproutCore Style Guide. Crockford’s guide recommends usage
all the time, but specifically for comparing against false values (those
values that are coerced to false
, such
as 0
, the empty string, null
, and undefined
). The jQuery Core Style Guide allows
the use of ==
for comparison against null
when the intent is to test for both
null
and undefined
. I recommend using ===
and !==
all the time without exception.
JSLint warns about all uses of ==
and !=
by
default. JSHint warns about using ==
and !=
when comparing to a false value by default. You can enable warnings for
all uses of ==
and !=
by adding the eqeqeq
option.
eval()
The eval()
function takes a string of JavaScript code and executes it. This
function allows developers to download additional JavaScript code, or to
generate JavaScript code on the fly, and then execute it. For
example:
eval("alert('Hi!')"); var count = 10; var number = eval("5 + count"); console.log(number); // 15
The eval()
function isn’t the
only way to execute a JavaScript string from within JavaScript. The same
can be done using the Function
constructor as well as setTimeout()
and setInterval()
. Here are some
examples:
var myfunc = new Function("alert('Hi!')"); setTimeout("document.body.style.background='red'", 50); setInterval("document.title = 'It is now '" + (new Date()), 1000);
All of these are considered bad practice by most of the JavaScript
community. Although eval()
may be used
from time to time in JavaScript libraries (mostly in relation to JSON),
the other three uses are rarely, if ever, used. A good general guideline
is to never use Function
and to use
eval()
only if no other options are
present. Both setTimeout()
and setInterval()
can be used but should use function instead of strings:
setTimeout(function() { document.body.style.background='red'; }, 50); setInterval(function() { document.title = 'It is now ' + (new Date()); }, 1000);
Crockford’s Code Conventions forbids the use of eval()
and Function
, as well as setTimeout()
and setInterval()
when used with strings. The jQuery
Core Style Guide forbids the use of eval()
except for a JSON parsing fallback used
in one place. The Google JavaScript Style Guide allows the use of eval()
only for converting Ajax responses into JavaScript values.
Both JSLint and JSHint warn about the use of eval()
,
Function
, setTimeout()
, and setInterval()
by
default.
Note
ECMAScript 5 strict mode puts severe restrictions on eval()
, preventing it from creating new
variables or functions in the enclosing scope. This restriction helps
close some of the security holes innate in eval()
. However, avoiding eval()
is still recommended unless there is
absolutely no other way to accomplish the task.
Primitive Wrapper Types
A little-known and often misunderstood aspect of JavaScript is the language’s
reliance on primitive wrapper types. There are three primitive wrapper
types: String
, Boolean
, and Number
. Each of these types exists as a constructor in the global scope and each
represents the object form of its respective primitive type. The main use
of primitive wrapper types is to make primitive values act like objects,
for instance:
var name = "Nicholas"; console.log(name.toUpperCase());
Even though name
is a string,
which is a primitive type and therefore not an object, you’re still able
to use methods such as toUpperCase()
as
if the string were an object. This usage is made possible
because the JavaScript engine creates a new instance of the String
type behind the scenes for just that
statement. Afterward, it’s destroyed, and another is created when it is
needed. You can test out this behavior by trying to add a property to a
string:
var name = "Nicholas"; name.author = true; console.log(name.author); // undefined
The author
property has vanished
after the second line. That’s because the temporary String
object representing the string was
destroyed after line 2 executed, and a new String
object was created for line 3. It’s
possible to create these objects yourself as well:
// Bad var name = new String("Nicholas"); var author = new Boolean(true); var count = new Number(10);
Although it’s possible to use these primitive wrapper types, I strongly recommend avoiding them. Developers tend to get confused as to whether they’re dealing with an object or a primitive, and bugs occur. There isn’t any reason to create these objects yourself.
The Google JavaScript Style Guide forbids the use of primitive wrapper types. Both JSLint and JSHint will warn if you try to use String
, Number
, or Boolean
to create new objects.
Get Maintainable JavaScript now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.