That's because closures are formed when a function is transported - and executed - outside of its lexical scope, while still keeping references to values within its lexical scope.
Closures are formed when a function is transported - and executed - outside of its lexical scope, while still keeping references to values within its lexical scope.
This statement is probably best illustrated with an example.
Example 1: String logger
In this example, we have two functions:
inner() is returned from inside outer. Due to the rules of lexical scoping, the
inner() function has access to the
let i variable declared in the scope of
outer(). I think most programmers are comfortable with that concept. The interesting part is that we then assign
outer() to another variable
runOuter, effectively transporting it's inner workings outside of its lexical scope.
This execution of the function in a different lexical scope is what creates the closure. We will follow the same pattern in the examples below.
Example 2: Log plus one
Continuing on very much the same idea as above, we create a function that logs a number in the outer function, but that number gets increased by one in the inner function. Also notice that, for illustration, the inner function in this case is anonymous; we don't need to refer to it by name anywhere for the code to work.
We can run
runLogger() anytime, and we can be sure it'll hold a reference to the currently iterated
Example 3: Array cycler
We're continuing with the same approach as above. Here's a function that uses a closure to keep track of state, in order to cycle over an array. The important concept here is that the state - in this case the current index at which the array is at - is packed into the
names() function. When
cycleNames() is called, the closure is referencing the
i variable that gets updated.
Let's take a step back.
All of these examples have a common thread running through them: a variable local to the outer function is being changed from the inner function. Then the outer function runs the inner function, so the inner function keeps a reference to its lexical scope regardless of where it's run. It can be executed anywhere that it can be transported to, and it will still keep that reference. That's what makes closures such a powerful construct.
Furthermore, this packing of data along with the functions that act on it can go a long way towards minimizing side-causes and side-effects.
Pretty cool stuff.
Bonus example: Nonce function
A bonus fourth example.
This nonce function executes only once, without having to reference a state variable declared outside of it's scope. It's the fact that it can execute only once without referencing a non-local variable that makes it portable, and more in line with functional programming, as it causes no side-effects.
Finally, if you are ever doubtful whether you are dealing with closures, a simple way to confirm their existence is through the Chrome Dev Tools. If you place a breakpoint in your code, you can inspect the scope manager at that point in time. The tool wil allow you to see which values are being closed over.