JavaScript uses functions to manage scope. A variable declared inside of a function is local
to that function and not available outside the function. On the
other hand,global variables are those declared
outside of any function or simply used without being declared. Every JavaScript environment has a global object accessible when you use
this outside of any function. Every
global variable you create becomes a property of the global object. In
browsers, for convenience, there is an additional property of the global
object called window that (usually)
points to the global object itself. The following code snippet shows how
to create and access a global variable in a browser environment:
myglobal = "hello"; // antipattern
console.log(myglobal); // "hello"
console.log(window.myglobal); // "hello"
console.log(window["myglobal"]); // "hello"
console.log(this.myglobal); // "hello"
1. The Problem with Globals
The problem with global variables is that they are shared among all the
code in your JavaScript application or web page. They live in the same
global namespace and there is always a
chance of naming collisions—when two separate parts of an application
define global variables with the same name but with different
purposes.
It’s also common for web pages to include code not written by the
developers of the page, for example:
A third-party JavaScript library
Scripts from an advertising partner
Code from a third-party user tracking and analytics
script
Different kinds of widgets, badges, and buttons
Let’s say that one of the third-party scripts defines a global
variable, called, for example, result. Then later in one of your functions
you define another global variable called result. The outcome of that is the last
result variable overwrites the
previous ones, and the third-party script may just stop working.
Therefore it’s important to be a good neighbor to the other
scripts that may be in the same page and use as few global variables as
possible. Later in the book you learn about strategies to minimize the
number of globals, such as the namespacing pattern or the self-executing
immediate functions, but the most important pattern for having fewer
globals is to always use var to declare
variables.
It is surprisingly easy to create globals involuntarily because of
two JavaScript features. First, you can use variables without even
declaring them. And second, JavaScript has the notion of implied globals, meaning that any
variable you don’t declare becomes a property of the global object (and
is accessible just like a properly declared global variable). Consider
the following example:
function sum(x, y) {
// antipattern: implied global
result = x + y;
return result;
}
In this code, result is used
without being declared. The code works fine, but after calling the
function you end up with one more variable result in the global namespace that can be a
source of problems.
The rule of thumb is to always declare variables with var, as
demonstrated in the improved version of the sum() function:
function sum(x, y) {
var result = x + y;
return result;
}
Another antipattern that creates implied globals is to chain assignments as part of a
var declaration. In the following
snippet, a is local but b becomes global, which is probably not what
you meant to do:
// antipattern, do not use
function foo() {
var a = b = 0;
// ...
}
If you’re wondering why that happens, it’s because of the
right-to-left evaluation. First, the expression b = 0 is evaluated and in this case b is not declared. The return value of this
expression is 0, and it’s assigned to the new local variable declared
with var a. In other words, it’s as
if you’ve typed:
var a = (b = 0);
If you’ve already declared the variables, chaining assignments is fine and
doesn’t create unexpected globals. Example:
function foo() {
var a, b;
// ...
a = b = 0; // both local
}
Note: Yet another reason to avoid globals is portability. If you want
your code to run in different environments (hosts), it’s dangerous to
use globals because you can accidentally overwrite a host object that
doesn’t exist in your original environment (so you thought the name
was safe to use) but which does in some of the others.
2. Side Effects When Forgetting var
There’s one slight difference between implied globals and explicitly
defined ones—the difference is in the ability to undefine these
variables using the delete
operator:
This shows that implied globals are technically not real
variables, but they are properties of the global object. Properties can
be deleted with the delete operator
whereas variables cannot:
// define three globals
var global_var = 1;
global_novar = 2; // antipattern
(function () {
global_fromfunc = 3; // antipattern
}());
// attempt to delete
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true
// test the deletion
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"
In ES5 strict mode, assignments to undeclared variables (such as
the two antipatterns in the preceding snippet) will throw an
error.
3. Access to the Global Object
In the browsers, the global object is accessible from any part of the code via
the window property (unless you’ve done
something special and unexpected such as declaring a local variable
named window). But in other
environments this convenience property may be called something else (or
even not available to the programmer). If you need to access the global
object without hard-coding the identifier window, you can do the following from any
level of nested function scope:
var global = (function () {
return this;
}());
This way you can always get the global object, because inside
functions that were invoked as functions (that is, not as constrictors
with new) this should always point to the global object. This is actually
no longer the case in ECMAScript 5 in strict mode, so you have to adopt
a different pattern when your code is in strict mode. For example, if
you’re developing a library, you can wrap your library code in an
immediate function and then
from the global scope, pass a reference to this as a parameter to your immediate
function.
4. Single var Pattern
Using a single var statement at the top of your functions is a useful pattern
to adopt. It has the following benefits:
Provides a single place to look for all the local variables
needed by the function
Prevents logical errors when a variable is used before it’s
defined (see Section 2.2.5)
Helps you remember to declare variables and therefore
minimize globals
Is less code (to type and to transfer over the wire)
The single var pattern looks like this:
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i,
j;
// function body...
}
You use one var statement and
declare multiple variables delimited by commas. It’s a good practice to
also initialize the variable with an initial value
at the time you declare it. This can prevent logical errors (all
uninitialized and declared variables are initialized with the value
undefined) and also improve the code
readability. When you look at the code later, you can get an idea about
the intended use of a variable based on its initial value—for example,
was it supposed to be an object or an integer?
You can also do some actual work at the time of the declaration,
like the case with sum = a + b in the
preceding code. Another example is when working with DOM (Document
Object Model) references. You can assign DOM references to local
variables together with the single declaration, as the following code
demonstrates:
function updateElement() {
var el = document.getElementById("result"),
style = el.style;
// do something with el and style...
}
5. Hoisting: A Problem with Scattered vars
JavaScript enables you to have multiple var statements anywhere in a function, and
they all act as if the variables were declared at the top of the
function. This behavior is known as hoisting. This can lead to
logical errors when you use a variable and then you declare it further
in the function. For JavaScript, as long as a variable is in the same
scope (same function), it’s considered declared, even when it’s used
before the var declaration. Take a
look at this example:
// antipattern
myname = "global"; // global variable
function func() {
alert(myname); // "undefined"
var myname = "local";
alert(myname); // "local"
}
func();
In this example, you might expect that the first alert() will prompt “global” and the second
will prompt “local.” It’s a reasonable expectation because, at the time
of the first alert, myname was not
declared and therefore the function should probably “see” the global
myname. But that’s not how it works.
The first alert will say “undefined” because myname is considered declared as a local
variable to the function. (Although the declaration comes after.) All
the variable declarations get hoisted to the top of the function.
Therefore to avoid this type of confusion, it’s best to declare upfront
all variables you intend to use.
The preceding code snippet will behave as if it were implemented
like so:
myname = "global"; // global variable
function func() {
var myname; // same as -> var myname = undefined;
alert(myname); // "undefined"
myname = "local";
alert(myname); // "local"
}
func();
Note: For completeness, let’s mention that actually at the
implementation level things are a little more complex. There are two
stages of the code handling, where variables, function declarations,
and formal parameters are created at the first stage, which is the
stage of parsing and entering the context. In the second stage, the
stage of runtime code execution, function expressions and unqualified
identifiers (undeclared variables) are created. But for practical
purposes, we can adopt the concept of hoisting,
which is actually not defined by ECMAScript standard but is commonly
used to describe the behavior.