- 3.1 Declaring Functions
- 3.2 Higher-Order Functions
- 3.3 Function Literals
- 3.4 Arrow Functions
- 3.5 Functional Array Processing
- 3.6 Closures
- 3.7 Hard Objects
- 3.8 Strict Mode
- 3.9 Testing Argument Types
- 3.10 Supplying More or Fewer Arguments
- 3.11 Default Arguments
- 3.12 Rest Parameters and the Spread Operator
- 3.13 Simulating Named Arguments with Destructuring
- 3.14 Hoisting
- 3.15 Throwing Exceptions
- 3.16 Catching Exceptions
- 3.17 The finally Clause
- Exercises
3.6 Closures
The setTimeout function takes two arguments: a function to execute later, when a timeout has elapsed, and the duration of the timeout in milliseconds. For example, this call says “Goodbye” in ten seconds:
setTimeout(() => console.log('Goodbye'), 10000)
Let’s make this more flexible:
const sayLater = (text, when) => { let task = () => console.log(text) setTimeout(task, when) }
Now we can call:
sayLater('Hello', 1000) sayLater('Goodbye', 10000)
Look at the variable text inside the arrow function () => console.log(text). If you think about it, something nonobvious is going on. The code of the arrow function runs long after the call to sayLater has returned. How does the text variable stay around? And how can it be first 'Hello' and then 'Goodbye'?
To understand what is happening, we need to refine our understanding of a function. A function has three ingredients:
A block of code
Parameters
The free variables—that is, the variables that are used in the code but are not declared as parameters or local variables
A function with free variables is called a closure.
In our example, text is a free variable of the arrow function. The data structure representing the closure stores a reference to the variable when the function is created. We say that the variable is captured. That way, its value is available when the function is later called.
In fact, the arrow function () => console.log(text) also captures a second variable, namely console.
But how does text get to have two different values? Let’s do this in slow motion. The first call to sayLater creates a closure that captures the text parameter variable holding the value 'Hello'. When the sayLater method exits, that variable does not go away because it is still used by the closure. When sayLater is called again, a second closure is created that captures a different text parameter variable, this time holding 'Goodbye'.
In JavaScript, a captured variable is a reference to another variable, not its current value. If you change the contents of the captured variable, the change is visible in the closure. Consider this case:
let text = 'Goodbye' setTimeout(() => console.log(text), 10000) text = 'Hello'
In ten seconds, the string 'Hello' is printed, even though text contained 'Goodbye' when the closure was created.
The fundamental idea of a closure is very simple: A free variable inside a function means exactly what it means outside. However, the consequences are profound. It is very useful to capture variables and have them accessible indefinitely. The next section provides a dramatic illustration, by implementing objects and methods entirely with closures.