Skip to content

Instantly share code, notes, and snippets.

@GA-MEB
Last active June 29, 2016 14:54
Show Gist options
  • Save GA-MEB/1f534969a75cf8b9bc447b499b6de94d to your computer and use it in GitHub Desktop.
Save GA-MEB/1f534969a75cf8b9bc447b499b6de94d to your computer and use it in GitHub Desktop.

Lesson : Scope and Closures in Javascript

Prerequisites

  • Pass by reference/value
  • Functions

Learning Objectives

  • 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.

Lesson Notes

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment