Closures and Scope

In JavaScript, variables defined using 'var' have function scope. This means that they normally exist from when the function they are declared in starts through until the point where the function finishes. It doesn't matter where inside of the function that you first reference the variable and it doesn't matter which of the references to the variable has 'var' attached. Regardless of where the 'var' is used the declaration of the variable is 'hoist' to the top of the function. Since the variables all exist from the start of the function we may as well put the 'var' to declare them all at the top so as to make the code easier to read by showing the declaration at the point in the code where the declaration of those variables actually occurs.

In the below code example, middle and c both exist from when the outer function is first created through to when it finishes running regardless of whether we have that var statement at the top of the code where it is shown or whether we were to place it at the bottom after the console.log() call. Similarly within the middle function it doesn't matter whether the var appears first in the function or after the 'return inner;' statement, both of those variables exist from the start of the middle function until the end of the function.

Because the inner function is defined inside of the middle function it has access to all of the same variables as the middle function does. The 'd' variable is still in scope within the inner function as it is in scope for everything within the middle function regardless of whether the code is directly in the middle function or within a function that is defined inside of the middle function.

var outer = function() {
"use strict";
var middle, c;
middle = function(a,b) {
var inner, d;
inner = function() {return d;};
d = a + b;
return inner;
c = middle(5,7);

If you reread the second sentence of the first paragraph above you will see that I said "normally exist". This is because there is a situation where a variable can continue to exist even after the function that it was defined in has gone out of scope. The circumstances under which this can occur is demonstrated by the above example code and is what is known as a closure.

When the above outer function is run that function runs the middle function passing 5 and 7 as 'a' and 'b'. The statement adding the two numbers together inside of the middle function then runs setting d equal to 12. The inner function is then returned and assigned to 'c'.

It is now possible to run the inner function from the outer function even though the middle function has finished running because we have provides access to the inner function to the outer one by returning it from the middle function and assigning it to c. So running c() effectively runs the inner function that was defined inside of the middle function. This access is possible because the function was returned from the middle function just before the middle function passed out of scope and it was assigned to a variable with outer function scope and so continues to be accessible while that variable remains in scope.

If the value returned were just a primitive value such as a number or string then we would have no further access to anything inside of the middle function after it returned that value to the outer function. Because the value returned in this example is a function the return uses 'pass by reference' instead of 'pass by value'. This means that the inner function and everything it references continues to exist even though the middle function has finished running. Returning anything other than a primitive value will return a reference to that object rather than just its value and so the element returned will continue to exist after the scope it was defined in ceases to exist.

Note that in this example it is not inner that continues to exist in this situation but rather the anonymous function that was assigned to it within middle. We cannot refer to inner anywhere after middle has finished running because it no longer exists. The anonymous function it references inside of middle continues to exist though because we now have a new reference pointing to it from the outer function and so can reference it using 'c'.

There is in fact one primitive value that does continue to exist even after the function it is declared in has finished running and that is 'd'. Because the anonymous function that is assigned to inner refers to 'd' and 'd' is in scope at the time the anonymous function is defined, that variable will continue to be in scope for as long as there is a way to access the anonymous function.

Variables only pass out of scope when there is no longer any way of referencing them. While this normally occurs when the function they are defined in finishes, they can continue to exist after the function is finished if the function created a way for it to be referenced from a wider scope. In the above code example the console.log call will log the value returned by running c(). The function currently attached to c() is the one originally defined inside of middle and assigned to inner. It can be referenced as 'c' because the reference to the function was returned at the end of middle and assigned to 'c'. This function references the variable 'd' that was defined inside of the middle function and which was in scope when the function currently referenced via 'c' and originally referenced via 'inner' was defined and so continues to be in scope as this function still provides a way to reference it. This means that calling c() returns the value 12 that was the value assigned to 'd' during the execution of middle.

Where references rather than values are returned from a function, those variables that the element referenced uses that were in scope at the time the element was created will continue to be in scope until such time as the last variable using that reference ceases to exist. Both 'd' and the anonymous function originally assigned to inner will move out of scope when the variable 'c' that provides a reference to them goes out of scope when outer finishes running unless there is code that passes a reference to it to a variable with even wider scope.


This article written by Stephen Chapman, Felgall Pty Ltd.

go to top

FaceBook Follow
Twitter Follow