- Pass by reference/value
- Functions
- Determine the value of a given variable using knowledge of functional scope.
- Demonstrate how function scope limits access to variables.
- Use a simple javascript closure to make a counter.
- Create a more complicated closure that returns a collection of functions.
By now you should already be familiar with defining and invoking functions.
You’ve probably seen things like this.
a
is defined inide the function and is available inside the function.
var myFunction = function(){
var a = 10;
console.log(a);
};
myFunction(); // 10
You’ve also probably seen things like this.
Even though a
is not defined inside the function,
it is accessible from within the function.
var a = 5;
var myFunction = function(){
console.log(a);
};
myFunction(); // 5
What about the reverse?
If we define a
inside the function, can we access it from outside?
var myFunction = function(){
var a = 10;
};
myFunction();
console.log(a); // ERROR
No, we can't.
What happens here?
var a = 5;
var myFunction = function(){
var a = 10;
console.log(a);
};
myFunction();
Poll: What value will myFunction
print when it is invoked?
- 5
- 10
- undefined
a
is defined both inside and outside the function,
and both definitions have different values.
When our code tries to find a value for a
,
it first checks the set of variables inside of the function
(i.e. the function’s scope).
If it doesn’t find a value, it then checks the next outermost scope.
In JavaScript, new scopes can only be created by defining new functions. The golden rule is to think of scope as bring 'one-way glass', like in those cop shows. You can always see 'out', but never 'in'... Almost. There is a loophole in this system that allows us to get inside a function's scope, but only when the function is invoked.
Consider a function defined inside another function.
var outerFunction = function(){
var a = 100;
var innerFunction = function(){
console.log(a);
};
innerFunction();
};
outerFunction(); // 100
Invoking the outer function also invokes the inner function,
which prints the value of a
.
Clearly, the console.log
inside innerFunction
has access to a
,
since it’s in an outer scope.
What if we were to return innerFunction
out of outerFunction
,
and invoke it there? What would happen?
var outerFunction = function(){
var a = 100;
var innerFunction = function(){
console.log(a);
};
return innerFunction;
};
var refToInnerFunc = outerFunction(); // `outerFunction` returns a reference to the function `innerFunction`
refToInnerFunc();
Since innerFunction
has a
in its scope,
by passing out a reference to innerFunction
,
we give ourselves the ability to (indirectly) interact with a
from outside outerFunction
, even after outerFunction has finished executing!
In JavaScript, this function innerFunction
is called a "closure".
To cite the MDN:
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
Although our outer function can only return one value, we’re not restricted to passing out a single function; rather, we can pass out a collection of functions (e.g. an object with methods).
var outerFunction = function(){
var a = 100;
return {
increment: function() { a++; },
print: function() { console.log(a); }
};
};
var returnedObject = outerFunction();
returnedObject.print(); // 100
returnedObject.increment();
returnedObject.print(); // 101
Let’s rewrite the previous example as a factory for ‘Counter’ objects, using the ‘factory function’ pattern.
A 'factory function' is a function you create that returns new objects
each time that it is invoked it. Usually, these objects are pretty similar.
However, a factory function is distinct from a constructor, not least in that
(a) there is no prototype which all objects share, and
(b) there is no use of the new
keyword
var counterFactory = function(){
var count = 0;
return {
increment: function() { count++; },
print: function() { console.log(count); }
};
};
var counter = counterFactory(); // counterFactory returns a 'counter' object
counter.print(); // 0
counter.increment();
counter.print(); // 1
Closures are extremely useful in JavaScript because, by default, all properties on all objects are publicly available. This is not very ‘safe’ as a programming practice. Using closures allow us to ‘hide’ variables by making them only accessible by invoking the closure functions.
One very common use case of closures is the IIFE (immediately invoked function expression):
var counter = (function(){ // same function as above!
var count = 0;
return {
increment: function() { count++; },
print: function() { console.log(count); }
};
})();
counter.print(); // 0
counter.increment();
counter.print(); // 1
In a way, you could say that an IIFE is a 'single-use factory function', since it is constructed in exactly the same way, but (because it is invoked immediately, and not stored in a variable), it cannot be invoked a second time.
The only way to access the count
variable is through the methods on counter
.
In effect, we have created an object with hidden properties and exposed methods.
This is called the JavaScript module pattern, and it's the secret sauce that
underlies all modern JS development frameworks, including Node.