Learn JavaScript in Construct, part 6: More on functions

18

Index

Stats

5,920 visits, 12,895 views

Tools

Translations

This tutorial hasn't been translated.

License

This tutorial is licensed under CC BY-NC 4.0. Please refer to the license text if you wish to reuse, share or remix the content contained within this tutorial.

Closures in brief

Closures are quite a complex topic, but we'll only cover it in brief here. In short, functions can be bundled together with things outside their own scope.

A good way to demonstrate this is with functions returning functions. Here's a function that returns another function that returns a message variable from the outer function.

function getFunction()
{
	let message = "Hello world!";

	return function()
	{
		return message;
	}
}

// Get a function returning 'message'
let returnedFunc = getFunction();

// Call it and log the result to the console
console.log(returnedFunc());

This still logs the message "Hello world!" to the console. However notice that the returned function refers to the message variable inside getFunction. Normally the message variable would only exist inside the body of getFunction, and stop existing after getFunction returns. However since another function refers to message, its lifetime is extended: it will exist as long as the returned function does, as it's effectively been bundled with that function. This is what closures do: they essentially bundle functions with things outside their scope.

Closures can also include function parameters. The example below demonstrates using a function to create another function that adds a number.

function makeAddFunction(addNumber)
{
	return function(n)
	{
		return n + addNumber;
	}
}

let add2 = makeAddFunction(2);
let add10 = makeAddFunction(10);

console.log(add2(5));	// 7
console.log(add10(5));	// 15

makeAddFunction returns a function that takes a parameter n, and adds addNumber to it. So makeAddFunction(2) returns a function that takes one parameter and adds 2 to it, named add2 in the above example. This function remembers the addNumber parameter that was originally passed, similar to the way message was remembered previously. It's also remembering the originally passed value separately for each returned function, so the add2 and add10 functions add different numbers.

Here's one more example that demonstrates that closures are persistent too. It uses a function that returns another function that increments a variable counter.

function makeCounterFunction()
{
	let counter = 0;
	
	return function()
	{
		return counter++;
	};
}

let counterFuncA = makeCounterFunction();
let counterFuncB = makeCounterFunction();

console.log(counterFuncA());	// 0
console.log(counterFuncA());	// 1
console.log(counterFuncA());	// 2

console.log(counterFuncB());	// 0

This works similarly to the previous example where message was remembered. However this example demonstrates that functions can also change the variable that is remembered. When counterFuncA is called three times, the value of the counter variable that it remembered is incremented, causing it to return an incrementing value.

The variable that is remembered is also unique to each function returned. Notice how calling counterFuncB returns 0 again. That's because it has its own copy of the counter variable that is incremented independently. In other words, each function returned by makeCounterFunction remembers its own separate copy of the counter variable, and can modify it independently.

It's also worth pointing out how the code outside makeCounterFunction has no way to modify the counter variable, as it's outside the variable's scope, so you can't write code that references it there. However the returned functions are bundled with the variable and can still use it in their function bodies. This provides a form of encapsulation: the counter variable is private to the returned function, as it's impossible to modify it by any means other than calling the returned function. For example it's impossible to write code that resets counter back to 0: the only way to support that would be if makeCounterFunction returned something different that specifically allowed that.

The full technical details about how closures work is quite an advanced topic, so we won't cover any more about closures here. However it's an important feature of JavaScript's functions that is useful to be aware of. In summary, JavaScript functions can hold on to things outside their scope, and keep using them after they would otherwise have stopped existing. Try experimenting with more examples to help improve your understanding. If this seems complicated, don't worry too much - it won't come up much more in the rest of the guide, and you can always revisit it later on. This section aims to just introduce you to the concept, so you at least know it's possible to do this.

Conclusion

In this part we've covered more about functions:

  • Function expressions, and how functions can be used like any other data type, such as numbers and strings
  • Passing functions as parameters to other functions (aka "callbacks")
  • Returning functions from functions
  • Recursion
  • Arrow functions as a shorter way of writing function expressions
  • An introduction to closures

There is even more to functions in JavaScript - we could write an entire guide on functions alone! However we've covered most of the key features of functions that are relevant to beginners, and so it's time to move on to the next topic. In the next part of this guide we'll move on to objects.

Learn more

If you want to dig deeper, you can learn more about the features mentioned in this guide at the following MDN Web Docs links:

Part 7

When you're ready to continue, head on to the next part at Learn JavaScript in Construct, part 7: Objects!

Next Tutorial In Course

Learn JavaScript in Construct, part 7: Objects 20:49

Introduces objects, another key fundamental of JavaScript programming.

  • 0 Comments

  • Order by
Want to leave a comment? Login or Register an account!