beginning javascript
functions closures

Functions and Closures

Implicit vs. Explicit

In JavaScript, we can define functions that receive named parameters. For example:

function printArgs(a, b) {
  console.log(a, b);
}

printArgs(10, 20, 30, 40);

The printArgs function receives a and b as arguments. We call these explicit parameters, but every JavaScript function also has implicit parameters, even if it was defined without any explicit parameters. An example of an implicit parameter is the arguments parameter. You can access an arguments parameter inside any function in JavaScript. For example, to access the third and fourth arguments passed to printArgs, we can use arguments elements:

function printArgs(a, b) {
  // Implicit Parameter: arguments
  console.log(a, b);
  console.log(arguments[2], arguments[3]);
}

printArgs(10, 20, 30, 40);

Since we have arguments as an implicit parameter, that word is a reserved one. For example, we cannot use the word arguments as an explicit parameter:

function printArgs(a, b, arguments) { }
// SyntaxError: arguments is a reserved word

The code above will fail because we are attempting to define an explicit arguments parameter. This is invalid in strict mode.

Strict mode is a way to opt in to a restricted variant of JavaScript. It is the default mode in modern JavaScript environments. Your code should not rely on anything that is only allowed in non-strict mode.

Exercise:

Explore the implicit arguments parameter in the printArgs example above. See what it holds. Pass different arguments to the printArgs function and see how that affects the implicit arguments parameter. What happens when you do not pass any arguments?

The arguments Parameter

The implicit arguments parameter is an object representing the array of explicit arguments provided in the function call. The implicit arguments parameter is not exactly an array but can be mostly treated like one. We can loop over it and use its items one by one just like an array.

For example, say that we want to write a function that returns the sum of all its arguments no matter how many of those we pass.

Let’s name the function addArgs and test it by calling it with a different number of arguments:

function addArgs() {
   // ...
}

addArgs(4, 5, 6)          // should return 15
addArgs(1, 1, 1, 1, 1, 1) // should return 6

Since we are dealing with a dynamic number of arguments here, we cannot explicitly name them. We have to use the implicit arguments parameter. Here is a possible implementation:

function addArgs() {
  let sum = 0;

  for (let i = 0; i < arguments.length; i++) {
    sum += arguments[i];
  }

  return sum;
};

In the code above, we defined a sum container and looped over the implicit arguments parameter as if it was an array. For each item, we added to sum the value of the arguments object at that index and finally returned sum.

Exercise:

Try to write a multiplyArgs function that returns the product of all of its arguments no matter how many it receives:

Here is a starting template:

function multiplyArgs() {
  // Implement...
};

multiplyArgs(4, 5, 6);    // should return 120
multiplyArgs(2, 2, 2, 2); // should return 16

Rest Parameters

The implicit arguments parameter is not exactly an array. In the addArgs example, if we attempt to use a forEach call on arguments instead of a regular for loop, the code will not work.

function addArgs() {
  let sum = 0;

  arguments.forEach(arg => { sum += arg; });

  return sum;
};

// TypeError: arguments.forEach is not a function

This is because the arguments object is not exactly an array. We cannot directly use most of the array methods on arguments. If we need to use array methods, we will have to first convert this arguments parameter into an array or use the call function prototype trick instead.

Using the call trick is actually pretty easy. We can simply grab a reference to the array method that we want to invoke (using the Array prototype object for example). We can then call the call function on that reference, passing in the arguments parameter as the first argument and any explicit parameters that we want to pass to the original method as the remaining arguments of the call function.

// Instead of: arguments.arrMethod(arg1, arg2)
// We can do: arrMethodRef.call(arguments, arg1, arg2)

For the addArgs example, we can get a reference to forEach using Array.prototype.forEach and then call the call function on that:

function addArgs() {
  let sum = 0;

  Array.prototype.forEach.call(arguments, arg => {
    sum += arg;
  });

  return sum;
};

Note how we passed the arguments object as the first argument to the call function. We passed the original forEach function callback handler as the second argument to the call function. This will work fine.

However, in modern JavaScript, instead of using the call trick with the arguments parameter, there is an easier way to treat a dynamic list of arguments as a native array. We can use Rest Parameters to create an array from the explicit arguments provided in the function call.

We just define a named argument with three dots before it.

function addArgs(...args) { }

The three-dots operator is called the rest operator. Using it before the last explicit argument name converts that named argument into an array of all argument values provided in that position.

The variable args above will be an actual JavaScript array object. So we can use all array methods on it, like forEach for example, to do our addArgs example without a need to use the call function. Or better yet, we can just use the reduce method on this new args array to sum the content with a one-liner:

function addArgs(...args) {
  return args.reduce((acc, curr) => acc + curr);
};

The reduce method is explained in this article.

Exercise:

Combine a REST parameter with other explicitly named arguments and test to see what happens.

Closures

JavaScript closures are like car functions — they come with various components that can be in various positions specific to that car.

Every function in JavaScript has a closure and this is one of the coolest features of the JavaScript language. Without closures, it would be difficult to implement common structures like callbacks or event handlers.

We create a closure whenever we define a function. Then, when we execute a function, its closure enables it to access data in its scopes.

It is kind of like when a car is manufactured (defined), it comes with a few functions like start, accelerate, decelerate. These car functions get executed by the driver every time they operate the car. Closures for these functions come defined with the car itself and they close over variables they need to operate.

Let’s narrow this analogy to the accelerate function. The function definition happens when the car is manufactured:

function accelerate(force) {
  // Is the car started?
  // Do we have fuel?
  // Are we in traction control mode?
  // Many other checks...


  // If all good, burn more fuel depending on the force
  // variable (how hard we\'re pressing the gas pedal)
}

Every time the driver presses the gas pedal, this function gets executed. Note how this function needs access to a lot of variables to operate, including its own force variable. But more importantly, it needs variables outside of its scope that are controlled by other car functions. This is where the closure of the accelerate function (which we get with the car itself) comes in handy.

Here is what the accelerate function’s closure promised to the accelerate function itself:

Ok accelerate, when you get executed, you can access your force variable, the isCarStarted variable, the fuelLevel variable, and the isTractionControlOn variable. You can also control the currentFuelSupply variable that we are sending to the engine.

Note that the closure did not give the accelerate function fixed values for these variables, but rather permission to access those values at the time the accelerate function is executed.

Closures are closely related to function scopes. When we execute a function, a private function scope is created and used for the process of executing that function. Then, these function scopes get nested when you execute functions from within functions (which is a common practice).

A closure is created when we define a function, not when we execute it. Then, every time we execute that function, its already-defined closure gives it access to all the function scopes available around it.

In a way, you can think of scopes as temporary (the global scope is the only exception to this), while you can think of closures themselves as permanent.

learndoc1
A closure as reported in Chrome dev-tools

To truly understand closures and the role they play in JavaScript, you first need to understand five simple concepts about JavaScript functions and their scopes.

Functions are Assigned by Value Reference

When you put a function in a variable like this:

function sayHello() {
  console.log("hello");
};

var func = sayHello;

You are assigning the variable func a reference to the function sayHello, not a copy. Here, func is simply an alias to sayHello.

Anything you do on the alias you will actually be doing on the original function. For example:

func.answer = 42;

console.log(sayHello.answer); // prints 42

The property answer was set directly on func and read using sayHello, which works.

You can also execute sayHello by executing the func alias:

func(); // prints "hello"

Scopes Have a Lifetime

When you call a function, you create a scope during the execution of that function. Then that scope goes away.

When you call the function a second time, you create a new different scope during the second execution. Then this second scope goes away as well.

function printA() {
  console.log(answer);
  var answer = 1;
};


printA();
// This first call creates a new scope
// which gets discarded right after


printA();
// This second call creates a new different scope
// which also gets discarded right after;

These two scopes that were created in the example above are different. The variable answer here is not shared between them at all.

Every function scope has a lifetime. They get created and discarded right away. The only exception to this fact is the global scope, which does not go away as long as the application is running.

Closures Span Multiple Scopes

When you define a function, a closure gets created.

Unlike scopes, closures are created when you define a function, not when you execute it. Closures also do not go away after you execute the function.

You can access the data in a closure long after a function is defined and after it gets executed as well.

A closure encompasses everything the defined function can access. This includes the defined function’s scope, all the nested scopes between the global scope and the defined function scope, and the global scope itself.

var G = 'G';

// Define a function and create a closure
function functionA() {
  var A = 'A'

  // Define a function and create a closure
  function functionB() {
    var B = 'B';
    console.log(A, B, G);
  }

  functionB();  // prints A, B, G
  // functionB's closure does not get discarded

  A = 42;

  functionB();  // prints 42, B, G
}

functionA();

When we define functionB here, its created closure will allow us to access the scope of functionB, plus the scope of functionA, plus the global scope.

Every time we execute functionB, we can access variables B, A, and G through its previously created closure. However, that closure does not give us a copy of these variables but rather a reference to them. So if, for example, the value of the variable A gets changed at some point after the closure of functionB is created, when we execute functionB after that, we will see the new value, not the old one. The second call to functionB prints 42, B, G because the value of variable A was changed to 42 and the closure gave us a reference to A, not a copy.

Do Not Confuse Closures With Scopes

It is common for closures to be confused with scopes, so let’s make sure not to do that.

// scope: global
var a = 1;
void function one() {
  // scope: one
  // closure: [one, global]

  var b = 2;
  void function two() {
    // scope: two
    // closure: [two, one, global]

    var c = 3;
    void function three() {
      // scope: three
      // closure: [three, two, one, global]

      var d = 4;
      console.log(a + b + c + d); // prints 10
    }(); // three

  }(); // two

}(); // one

In the simple example above, we have three functions that are all defined and immediately invoked, so they all create scopes and closures.

The scope of function one() is its body. Its closure gives us access to both its scope and the global scope.

The scope of function two() is its body. Its closure gives us access to its scope, plus the scope of function one(), plus the global scope.

And similarly, the closure of function three() gives us access to all scopes in the example. This is why we were able to access all variables in function three().

But the relation between scopes and closures is not always this. Things get complicated when the defining and invoking of functions happen in different scopes. Let me explain that with an example:

var v = 1;

var f1 = function () {
  console.log(v);
}

var f2 = function() {
  var v = 2;

  f1(); // *Will this print 1 or 2?*
}; f2();

What do you think the above example will print? The code is simple; f1() prints the value of v, which is 1 on the global scope, but we execute f1() inside of f2(), which has a different v that is equal to 2. Then we execute f2().

Will the previous code print 1 or 2?

If you are tempted to say 2, you will be surprised. This code will actually print 1. The reason is, scopes and closures are different. The console.log line will use the closure of f1(), which is created when we define f1(). This means the closure of f1() gives us access to only the scope of f1() plus the global scope. The scope where we execute f1() does not affect that closure. In fact, the closure of f1() will not give us access to the scope of f2() at all. If you remove the global v variable and execute this code, you will get a reference error:

var f1 = function () {
  console.log(v);
}

var f2 = function() {
  var v = 2;

  f1(); // ReferenceError: v is not defined
}; f2();

This is very important to understand and remember.

Closures Have Read and Write Access

Since closures give us references to variables in scopes, the access that they give us means both read and write, not just read.

Take a look at this example:

function outer() {
  let a = 42;

  function inner() {
    a = 43;
  }

  inner();

  console.log(a);
}

outer();

The inner() function here, when defined, creates a closure that gives us access to the variable a. We can read and modify that variable and if we do modify it we will be modifying the actual a variable in the outer() scope.

This code will print 43 because we used the inner() function’s closure to modify the outer() function’s variable.

This is actually why we can change global variables everywhere. All closures give us both read and write access to all global variables.

Closures Can Share Scopes

Since closures give us access to nested scopes at the time we define functions, when we define multiple functions in the same scope that scope is shared among all created closures. Because of this, the global scope is always shared among all closures.

function parentFunction() {
  let a = 10;

  function double() {
    a = a + a;
    console.log(a);
  };

  function square() {
    a = a * a;
    console.log(a);
  }

  return { double, square };
}

let { double, square } = parent();

double(); // prints 20
square(); // prints 400
double(); // prints 800

In the example above, we have a parentFunction() function with variable a set to 10. We define two functions in this parentFunction() scope, double() and square(). The closures created for double() and square() both share the scope of parentFunction(). Since both double() and square() change the value of a, when we execute the last three lines, we double a (making a = 20), then square that doubled value (making a = 400), and then double that squared value (making a = 800).

Exercise:

Let’s check your understanding of closures. Before you execute the following code, try to figure out what it will print:

let a = 1;
const function1 = function() {
  console.log(a);
  a = 2;
}

a = 3;

const function2 = function() {
  console.log(a);
}

function1();
function2();