Functions With Memories
Returning a function from another function might look alright, but it's a real gotcha of code that requires precision to understand.
Once we've found the balance between research and implementation, we're ready to tackle the most profound concept in JavaScript: Closure.
Returning a function from another function is one of the most powerful features in our toolkit. It allows us to use tools like once and memoize, implement the module pattern, and even enables asynchronicity to function. But it all starts from a point of great similarity: the execution context.
The Essentials
- Execution Context: Every time we run a function, we create a new local memory (variable environment) that is temporary and deleted after the function finishes.
- Function Definitions: Functions are stored as a bunch of text (code) in memory. They can be returned just like any other data.
- The "Disconnected" Return: Once a function is returned from an outer function, the outer function's execution context is popped off the stack and forgotten.
The Function Generator: createFunction
Let's look at a piece of code that may look fairly alright but requires a lot of precision. We're going to define a function called createFunction.
function createFunction() {
function multiplyBy2(num) {
return num * 2;
}
return multiplyBy2;
}
const generatedFunc = createFunction();
const result = generatedFunc(3); // 6When we "turn on" JavaScript, our thread of execution kicks off in the Global Execution Context. We store createFunction in global memory as a bunch of text.
Step 1: Running createFunction
Next, we declare a constant generatedFunc. To work out what to store there, we must run createFunction. This creates a new execution context, which is added to the Call Stack.
Inside createFunction, we have a local memory. We declare multiplyBy2 and store its function definition. Then we hit the key line: return multiplyBy2.
JavaScript grabs that block of code (just that function definition) and returns it. The createFunction execution context is then popped off the call stack and forgotten. All labels inside are deleted.
Step 2: Saving the Definition
The returned code is assigned to the global constant generatedFunc. At this point, generatedFunc has absolutely nothing to do with createFunction anymore. It is simply a label for the code: num => num * 2.
Step 1:In global memory, we store 'createFunction'. We then reach line 7 and invoke it.
The Distortion
As humans, when we see generatedFunc(3), we feel a connection back to createFunction because we saw it assigned in the line above. But for JavaScript, that connection is gone. It ran createFunction one time, got the code, and moved on.
Why would we put a function inside another function only to return it? It seems like extra steps. But if we had functions that could remember their previous invocation (not just temporary memory, but a persistent one) that would be remarkable.
In the next part, we'll solve the puzzle of how a function can "remember" data from its birthplace even after that birthplace is gone.
Further Reading and Watching
- Video: The Hard Parts: createFunction Trace
- MDN: First-class Functions
- Concept: Execution Context and Call Stack
Keep reading