WEBSITE

JavaScript Patterns : Minimizing Globals

9/23/2010 11:41:43 AM
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:

  • Globals created with var (those created in the program outside of any function) cannot be deleted.

  • Implied globals created without var (regardless if created inside functions) can be deleted.

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.
Other  
 
Most View
Programming Windows Services with Microsoft Visual Basic 2008 : Extending the WMI Implementation
Reminders – The Best Geographical Reminders
The MiFi Liberate - Will The World Need A Mobile Hotspot With A Touchscreen? (Part 2)
The State Of Mobile Processors (Part 1)
Epson Expression XP-605 – Versatile Home Printer
Microsoft Dynamics Sure Step 2010 : Solution selling concepts
Suryl Guardian - Protect Your PC From Malware And Other Problems
Tecdesk Smart 5500 (Part 2)
Chat In The Dock (Part 2)
Advanced Compacts - Pocket Power House (Part 2) : Nikon COOLPIX P7700, Olympus XZ-2
Top 10
Porsche Cayenne Turbo Versus Mercedes ML63 AMG – Battle Of The Behemoths (Part 2)
Porsche Cayenne Turbo Versus Mercedes ML63 AMG – Battle Of The Behemoths (Part 1)
The BMW I8 – Car Of The Future (Part 4)
The BMW I8 – Car Of The Future (Part 3)
The BMW I8 – Car Of The Future (Part 2)
The BMW I8 – Car Of The Future (Part 1)
McLaren P1, Porsche 918, And Ferrari LaFerrari – A Brave New Breed (Part 5)
McLaren P1, Porsche 918, And Ferrari LaFerrari – A Brave New Breed (Part 4)
McLaren P1, Porsche 918, And Ferrari LaFerrari – A Brave New Breed (Part 3)
McLaren P1, Porsche 918, And Ferrari LaFerrari – A Brave New Breed (Part 2)