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.

April 25, 20264 min read1 / 3

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

  1. 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.
  2. Function Definitions: Functions are stored as a bunch of text (code) in memory. They can be returned just like any other data.
  3. 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.

JavaScript
function createFunction() { function multiplyBy2(num) { return num * 2; } return multiplyBy2; } const generatedFunc = createFunction(); const result = generatedFunc(3); // 6

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

JavaScript Execution Engine
Thread of Execution
1function createFunction() {
2 function multiplyBy2(num) {
3 return num * 2;
4 }
5 return multiplyBy2;
6}
7const generatedFunc = createFunction();
8const result = generatedFunc(3);

Step 1:In global memory, we store 'createFunction'. We then reach line 7 and invoke it.

Memory
createFunctionf
Call Stack
Global
Bottom of Stack

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