Scopes in JavaScript

Charles Hughes
4 min readMar 28, 2014

--

This is part four of a five part series. For more context see the previous three posts: Understanding Function Objects In JavaScript, What is “this”?, and Bind, call, and apply. Oh my!

Before I blow your mind with closure scopes, I need to explain what scopes are in general in case you don’t understand them. A scope in JavaScript can be thought of as simply an object in memory that is used as a key value store that is used whenever a variable name is referenced in your program. What I’m going to do is build a scoping system that mimicks JavaScript’s scoping system in JavaScript. This will start with defining an object constructor we could call makeScope:

var makeScope = function(){
var instance = {};
var storage = {};
instance.define = function(varName, value){
storage[varName] = value;
};
instance.lookup = function(varName){
return storage[varName];
};
return instance;
};

It’s okay if you don’t understand exactly what I am doing here. I’m using closure scopes and functional instantiation, which I do not expect you to know yet. Just trust that the above code works and read on.

The above code is an example of a datastructure that might be used to implement scoping in a programming language that doesn’t have scope hierarchies for some reason. I’m leaving out scope hierarchies for the moment to keep things simple. I’m now going to walk you through how this datastructure would be used in a Javascript program instead of JavaScript’s actual scoping system.

Each method in the scope object defined above has an analog usage in JavaScript programming. First there is the function define. Define takes a variable name and the value it will be assigned to and adds them to our storage object. So “var x = 1;” is equivalent to the statement “scope.define(‘x’, 1);”. The second function is lookup. Lookup takes a variable name and attempts to find the value corresponding to that name in our storage object. So “return x;” is equivalent to “return scope.lookup(‘x’);”. Now lets write some regular JavaScript code and then show what it would look like if we used only our new custom scoping system:

var n = 11;
var test = function(){
var x = n;
if( x % 2 ){
return x;
}
};
test();

is equivalent to:

var globalScope = makeScope();
globalScope.define(‘n’, 11);
globalScope.define(‘test’, function(){
var localScope = makeScope();
localScope.define(‘x’, localScope.lookup(‘n’));
if( localScope.lookup(‘x’) % 2 ){
return localScope.lookup(‘x’);
}
};
globalScope.lookup(‘test’)();

The second code block is what your every day code might look like if JavaScript didn’t implement scopes as part of their language standard. I’m basically explicitly doing what is implicitly done in the first code block. So you will notice that I have added two entirely new lines to the code: “var globalScope = makeScope();” and “var localScope = makeScope();”. These two lines need to be added so that we have a scope object to use at each scope level that would be created by JavaScript implicitly otherwise. The other substitutions were explained previously.

So the problem with this scoping system so far is that there is no scope hierarchy mechanism. This means that we could not reach anything in the globalScope from the localScope I defined in the test function. So that means that when we try to lookup ‘n’ on the localScope object, we will get undefined rather than 10. This is pretty sad. Lets add this functionality:

var makeScope = function(parent){
var instance = {};
var storage = {};
instance.define = function(varName, value){
storage[varName] = value;
};
instance.lookup = function(varName){
if(!storage[varName] && parent){
return parent.lookup(varName);
}else{
return storage[varName];
}
};
return instance;
};

What I have done above is added a parent scope mechanism to our scope datastructure. This will allow our localScope object to reference variables defined in the globalScope object. Check it out:

var globalScope = makeScope();
globalScope.define(‘n’, 11);
globalScope.define(‘test’, function(){
var localScope = makeScope(globalScope);
localScope.define(‘x’, localScope.lookup(‘n’));
if( localScope.lookup(‘x’) % 2 ){
return localScope.lookup(‘x’);
}
});
globalScope.lookup(‘test’)();

So when we execute the line “localScope.define(‘x’, localScope.lookup(‘n’));” in the function “test”, the lookup function will see that there is no value ‘n’ in localScope. After discovering that, the lookup function executes the lookup function on its parent scope (in this case globalScope). If globalScope didn’t have any ‘n’ key value pair, then it would call lookup on its parent as well if it had one (I guess globalScope is a little like Batman now that I think about it).

Now that we have added scope hierarchies to our system, the above code should work! If you compare the above code to the original little blurb we have near the top of this post, you can plainly see why these kinds of things are simply implied. But I think that it helps a lot to write it out explicitly so you can know almost exactly what is going on under the hood.

Now that you understand (hopefully) how scope lookups work, there is one more piece of the puzzle to consider: garbage collection. When you reach a return statement in a function or simply the closing bracket of a function, the scope object that was created for that object invocation is no longer needed. So that scope object will likely have nothing that references it after the function closes out and it will be collected by JavaScript’s garbage collector. The interesting fact is that this isn’t always the case in JavaScript. There are conditions under which a function’s scope can still be referenced by other parts of the system long after the original function that generated it has closed out. You will learn all about this wizardry in the next post: “Closure Scopes: Closing is never over if something has been closed over”

This is part 4 of a 5 part series (1, 2, 3, 4, 5). The next post will be Closure scopes.

--

--