Ryan Kinal
100 E. St. Clair St.
Warren, PA 16365

Google ProfileTwitter: @IndigloMouth

Scope in JavaScript

... is a little crazy. It's not a normal kind of scope by any means, though once you think about it, it's not too hard to understand. All you really have to remember is JavaScript uses function scope, not block scope, and anything not specifically declared in a function is technically in the global scope. That can be confusing to wrap your head around, but here are some examples to help out.

Example 1: The Easy Example

var x = 27; var y = 12; var testing = function(x) { return (x === 12); } alert(testing(x)); alert(testing(y));

In lines 1 and 2, the variables x and y are clearly defined to be 27 and 12, respectively. The function takes one parameter - x - and tests to see if x is equal to 12. If there were no scope involved, it would always be false, since x is defined to be 27 in line 1. However, since JavaScript uses function scope, it creates a new variable x which is equal to the value of whatever was passed to the function. If that value is 12, as it is in the last line, the function returns true.

Example 2: The slightly confusing example

var x = 27; var y = 12; if (true) { var x = 39; } alert(x);

Again, x and y are defined to be 27 and 12. Then we enter a block, as defined by the curly braces ( '{' and '}' ). There, a new variable x is defined, with the value 39. However, since JavaScript does not use block scope, it simply overrides the original variable x, and the alert will show the new value of 39, instead of the old value of 27.

Example 3: The way more confusing example

var items = document.getElementsByTagName('li'); var i; for (i = 0; i < items.length; i++) { addEvent(items[i], 'click', function() { alert(items[i].innerHTML); }); }

Okay, so now were getting into DOM manipulation kinds of things. The first line gives us (essentially) an array of <li> objects, which we then loop over, adding a click event to each one (note that I'm using addEvent - a fairly common function with several implementations, one of which you can find here). The event handler is a simple function that will alert the innerHTML of the element that we're adding the event to.

... or so we thought. What actually happens is this: Since the variable i is defined outside of the function, it is considered as a global variable. When the function runs, it checks its own scope first, finding no variable i, so it checks the next scope up (in this case, the global scope). At run time, when the user clicks on a list item, the variable i has already been incremented past the bounds of the items array, and items[i] will return undefined.

Example 4: The "Oh I see" example

var items = document.getElementsByTagName('li'); var i; for (i = 0; i < items.length; i++) { addEvent(items[i], 'click', function(p_i) { return function() { alert(items[p_i].innerHTML); } }(i)); }

This example is very very similar to the last one, with one key difference. We're using a closure to set up an intermediate scope between the click handler and the global scope. Note that the third argument to addEvent - function(p_i) { ... } - is immediately called with a value of i (hence the (i) just before the closing perenthesis). That function runs, and returns a new function, which will be run when the user clicks a list item.

When that happens, the inner function runs, and checks its scope for the variable p_i. Since it doesn't have one, it looks in the next scope up, which is the outer function. And hey, there it is, declared as a parameter, and with whatever value it had when it was called - separate scopes for each call. In this case, it will be 0 for the first <li>, 1 for the second, and 2 for the third. It then uses that value to retrieve an item from the items array, and get the innerHTML from that item. Magically, it alerts the correct innerHTML.

Example 5: The "Huh. Didn't see that coming" example

var makeClick = function(p_i) { return function() { alert(items[p_i].innerHTML); } } var items = document.getElementsByTagName('li'); var i; for (i = 0; i < items.length; i++) { addEvent(items[i], 'click', makeClick(i)); }

If you read this function and said "There's no way that works", then you don't quite know as much about scope in JavaScript as you thought.

It does, in fact, work, because JavaScript checks scopes at runtime. This is exactly the same thing as the previous example, I just moved the closure from within the addEvent call to its own variable. It looks like items shouldn't be defined in makeClick, but at runtime, it is, and thus the function works.

Example 6: The Last Example

var inArray = function(p_val, p_arr) { for (i = 0; i < p_arr.length; i++) { if (p_arr[i] === p_val) { return true; } } return false; } var arrays = [ [3, 4, 5], [6, 7, 8], [9, 10, 11] ]; for (i = 0; i < arrays.length; i++) { if (inArray(11, arrays[i])) { alert('Found one!'); } }

This looks like a valid program, right? It looks like it will loop through all of the arrays, and return true on the last one.

But it won't.

Because the variable i is not specifically declared (using var) in the body of inArray, JavaScript assumes that it's a reference to the global i, which is also used outside of the function. If you look at it from that point of view, it gets to the first array (i = 0), then calls inArray, which then loops through until i is 3, and then returns false. Since i is now 3, which is greater than the number of arrays we have to check, it falls out of the main loop, and the program ends.

If, at the start of inArray, we put in one little line (var i;), then we avoid this problem.

So that's my demonstration of scope in JavaScript. It's a little odd, and definitely different that what you might expect, especially if you know anything about scope in other languages. The global object is a pain, but thanks to the fact that functions are a first-class data type, we can work around it with some degree of grace with closures.

Thomas Shields says:
This is great! Simple, straightforward, funny.
Marek says:
Hehe! Nice post! :) Like your style. I was writing &lt;a href=&quot;http://blog.i-evaluation.com/2012/08/10/javascript-confusing-bits/&quot;&gt;JavaScript Confusing Bits&lt;/a&gt; when I found your post. I think your reader might want to have look at broader article. I tried to elaborate bit more emphasizing other &quot;bits&quot; that got me lost last few months. Cheers.
Marek says:
sorry about mess in my comment. You should put information about how to publish links in comments to avoid that :)

Are you human? (enter yes if so)