Javascript Guide - Scope Resolution
You can now also navigate the guide through the table of contents.
In the previous post we learned about functions, and it is time we learn some of their key features.
We are now side-stepping to a more complex, yet vastly important aspect of javascript - the scope resolution mechanism - or - why do we keep on using var.
The global scope
First of all - what is a scope - a scope is the current stack of variables and functions that our program uses. In JS, this feature is quite complex, but also very powerful.
Up until now, whenever we declared variables, we did that in the global scope. In javascript, there is a hidden, object-like variable, that contains a list of all the variables and functions declared on our program. In the browser, that object is called window.
var a = 'abc';
console.log(window['a']);
Whenever we try to use a variable on the global scope, the window object is scanned for that variable's name, and returns its value.
But here is where it gets tricky - when we create a function, we create a new scope. This scope has access to all the variables of it's parent's scope:
var a = 'abc';
function useScope(){
console.log(a);
}
useScope();
As you see, although a was declared on a different scope, it is accessible everywhere within our program. It gets even better - we can also manipulate variables of outer scopes:
var a = 'abc';
function manipulateVar(){
a = '12a';
}
console.log(a); // abc
manipulateVar();
console.log(a); //12a
But as powerful as this might seem, it is also very risky.
Another important note is that whenever we create a variable without the var keyword, it will be created on the global scope:
function manipulateVar(){
a = '12a';
}
console.log(a); // undefined
manipulateVar();
console.log(a); //12a
These 2 features make it extremely easy to make mistakes. It is because of this that I told you that we should always use the var keyword. You see, whenever a variable is declared using the var keyword, it is accessible only to that scope and it's children.
So, if we want to make sure we are not manipulating global variables, we should do:
var a = 'abc';
function manipulateVar(){
var a = '12a';
}
console.log(a); //abc
manipulateVar();
console.log(a); //abc
In the above example, 2 as were defined, one for each scope. As a best practice, we should always use the var keyword, unless we explicitly want to access a variable from a parent scope.
Scope Nesting
If that wasn't confusing enough, there is more - since each function is a new scope, it has all the properties of a scope - and so we can declare whatever we want within it. That means that we can create new functions within that scopes, and these will have private scopes of their own.
var a = 'global -aa-',b='global -bb-',c='global -cc-';
function myScope(){
var c = 'myscope -ccc-';
function anotherScope(){
var b = 'anotherScope -bbb-';
console.log(a+b+c);
};
anotherScope();
console.log(a+b+c);
};
myScope();
console.log(a+b+c);
//global aa anotherScope bbb myscope ccc
//global aa global bb myscope ccc
//global aa globa bb gobal cc
This was by far the most complex example yet, for at least 3 reasons:
- Function Nesting - we have a function that is nested within another, and called by it.
- Execution order - the global scope calls
myScopethat callsanotherScope. - Scope nesting - each scope uses some of it's parent scopes' variable, and declares it's own. It is this confusion that we try to avoid unless necessary (and sometimes it can be extremely useful).
I suggest you try experimenting with this for a while. Although quite confusing, it is also mandatory that we understand this as we go along, at least at the base level.
Anonymous Closures
OK. This part will be a bit more advanced than what we've done so far. We will use a lambda to create a one-time scope. Before we begin however, I want us to understand how parenthesis work in JS
Whenever we put a statement inside ( ), that statement will be executed, and it's value will be returned. This has many uses, for example - numbers have a lot of methods attached to them, but the ., when used with a number, will simply be treated as a decimal separator. So, instead, we can do this:
(1.2).toString();
What we do with anonymous closures, is that we use the ( ) to invoke a lambda, and then execute it:
(function(){
//private_a will only be accessible within this scope
var private_a = 'a';
//since this one doesn't use var, it will be defined on the global scope
A = function(){console.log(private_a);}
})();
A(); //a
So, what we did here, is to create a private variable, that is only accessible to the code within the scope. The reason we would want to avoid what is called global scope pollution.
What this means is simple - when we write our webpages, we often find ourselves using other people code. If that code uses a lot of global variables, there is a good chance we would find ourselves stepping on other people's toes. By using the above pattern, we can create as many variables as we want, and still expose only the bare minimum to the global scope. It will also help us along the way to create a better encapsulated code.
Next Post - the Prototype