An Alternative Perspective

Reviewing our working definition for closure, the assertion is that functions are “first-class values” that can be passed around the program, just like any other value. Closure is the link-association that connects that function to the scope/variables outside of itself, no matter where that function goes.

Let’s recall a code example from earlier in this chapter, again with relevant scope bubble colors annotated:

  1. // outer/global scope: RED(1)
  2. function adder(num1) {
  3. // function scope: BLUE(2)
  4. return function addTo(num2){
  5. // function scope: GREEN(3)
  6. return num1 + num2;
  7. };
  8. }
  9. var add10To = adder(10);
  10. var add42To = adder(42);
  11. add10To(15); // 25
  12. add42To(9); // 51

Our current perspective suggests that wherever a function is passed and invoked, closure preserves a hidden link back to the original scope to facilitate the access to the closed-over variables. Figure 4, repeated here for convenience, illustrates this notion:

Function instances linked to scopes via closure

Fig. 4 (repeat): Visualizing Closures

But there’s another way of thinking about closure, and more precisely the nature of functions being passed around, that may help deepen the mental models.

This alternative model de-emphasizes “functions as first-class values,” and instead embraces how functions (like all non-primitive values) are held by reference in JS, and assigned/passed by reference-copy—see Appendix A of the Get Started book for more information.

Instead of thinking about the inner function instance of addTo(..) moving to the outer RED(1) scope via the return and assignment, we can envision that function instances actually just stay in place in their own scope environment, of course with their scope-chain intact.

What gets sent to the RED(1) scope is just a reference to the in-place function instance, rather than the function instance itself. Figure 5 depicts the inner function instances remaining in place, pointed to by the RED(1) addTo10 and addTo42 references, respectively:

Function instances inside scopes via closure, linked to by references

Fig. 5: Visualizing Closures (Alternative)

As shown in Figure 5, each call to adder(..) still creates a new BLUE(2) scope containing a num1 variable, as well as an instance of the GREEN(3) addTo(..) scope. But what’s different from Figure 4 is, now these GREEN(3) instances remain in place, naturally nested inside of their BLUE(2) scope instances. The addTo10 and addTo42 references are moved to the RED(1) outer scope, not the function instances themselves.

When addTo10(15) is called, the addTo(..) function instance (still in place in its original BLUE(2) scope environment) is invoked. Since the function instance itself never moved, of course it still has natural access to its scope chain. Same with the addTo42(9) call—nothing special here beyond lexical scope.

So what then is closure, if not the magic that lets a function maintain a link to its original scope chain even as that function moves around in other scopes? In this alternative model, functions stay in place and keep accessing their original scope chain just like they always could.

Closure instead describes the magic of keeping alive a function instance, along with its whole scope environment and chain, for as long as there’s at least one reference to that function instance floating around in any other part of the program.

That definition of closure is less observational and a bit less familiar-sounding compared to the traditional academic perspective. But it’s nonetheless still useful, because the benefit is that we simplify explanation of closure to a straightforward combination of references and in-place function instances.

The previous model (Figure 4) is not wrong at describing closure in JS. It’s just more conceptually inspired, an academic perspective on closure. By contrast, the alternative model (Figure 5) could be described as a bit more implementation focused, how JS actually works.

Both perspectives/models are useful in understanding closure, but the reader may find one a little easier to hold than the other. Whichever you choose, the observable outcomes in our program are the same.

NOTE:
This alternative model for closure does affect whether we classify synchronous callbacks as examples of closure or not. More on this nuance in Appendix A.