Each function creates a scope object that contains the arguments for that function and the local variables defined in that function.
Since Javascript uses lexical scoping, that means that a given piece of code can access variables defined within the current function or within any parent function. Javascript makes this work by maintaining a chain of scope objects so each scope has a reference to its parent scope.
When a given piece of code accesses a variable like:
foo = 1;
Javascript, looks in the current scope object to see if something named foo
exists there. If so, that variable is used. If not, it gets the reference to the parent scope, checks that scope for something named foo
and continues this process up the parent chain until it gets to the global scope and thus can go no further.
So, in this example:
function one() {
var foo = 2;
function two() {
var fee = 3;
function three() {
foo = 1;
}
three();
}
two();
}
one();
The function one()
will execute and define foo
and two
in its scope. It will then call two()
which will execute and define fee
and three
in its scope. It will then call three()
which will execute and attempt to change the value of foo
. It will first look in the scope object belonging to three
, but it will not find anything named foo
there so it will go to the parent scope of three
which will be the two
scope object. It will not find a variable named foo
in that scope object so it will go to the parent object of two
which will be the scope object of one
. There it will find a variable named foo
so it will change the value of that variable.
On thing that is different about Javascript scope objects compared to something like a C++ stack frame is that they can continue to live after the function that created them has finished executing. This is a very common structure in Javascript and is often referred to as a closure. Here's a simple example of that:
function initializeCntr() {
var cntr = 0;
document.getElementById("test").addEventListener("click", function(e) {
++cntr;
document.getElementById("clickCnt").innerHTML = cntr;
});
}
initializeCntr();
In this short code example, there are three scopes present, the global scope (that contains the initializeCntr
symbol), the initializeCntr
scope that contains the cntr
variable and the scope that belongs to the anonymous function that is set as the event handler.
When you call initializeCntr()
, it creates a function scope object that contains the variable cntr
. It then runs the addEventListener()
method and passes a reference to the inline anonymous event handler for addEventListener()
. The initializeCntr()
method then finishes execution. But, because the inline anonymous function passed as the event handler contains a reference to the cntr
variable in the initializeCntr
scope object, that scope object is NOT garbage collected even though initializeCntr()
has finished executing. Instead, it is kept alive because of the lasting reference to the cntr
variable in it. When that click event handler is then called sometime in the future, it can use that cntr
variable. In fact, the initalizeCntr
scope will be kept alive as long as the event handler is active. If the event handler is removed or the DOM object it is attached to is removed, then and only then would the initializeCntr
scope object be eligible for garbage collection and eventually get freed.
Some JS implementations are smart enough to not necessarily retain the entire scope object and everything in it, but instead to only retain the elements of the scope object that are specifically referenced in child scopes that are still active. So, if there were other variables next to cntr
that were not used in the event handler, those other variables could be freed.
FYI, this longer lasting scope is called a closure. Closures are very useful concepts in Javascript and something not available in a language like C++ (because they rely on garbage collection).