×
By the end of this chapter, you should be able to:
arguments
represents and how it can be usedOne of the most difficult concepts to understand when first learning JavaScript is closure
. Let's take a stab at a definition:
Closure is when a function is able to access variables from an outer function that has already returned.
Thanks to JavaScript's inner workings, a function is able to remember variables defined in functions even if that function has returned. Let's see what we mean by that with an example.
function outer(a){ return function inner(b){ return a + b; } } outer(5) // this returns the inner function // this calls the inner function right away outer(5)(2) // 7 // we can store the inner function in a variable let laterAdd = outer(10) // we can now call that inner function laterAdd(15) // 25 // but how was the inner function able to remember the parameter "a" defined in the outer function which already returned? The answer is through closure.
There are many use cases for closures. One of the more common ones is to create a "private variable," or a variable that can not be accessed directly (and overwritten). Here's an example that uses closure to create a "private" variable.
function defineAge(){ let age = 28; return function growUp(){ return ++age; } } let ageOnce = defineAge(); ageOnce(); // 29 ageOnce(); // 30
So who can access our age
variable? Only the defineAge
function, which has returned, and the growUp
function, which, through the use of closure, has access to the age
variable. Amazingly, this is true even though the outer function defineAge
has already returned. Our age
variable is now protected and no one can gain access to it! In cases like this, we say that growUp
(the inner function) has closure over the scope of defineAge
.
Let's look at another example:
function createInstructors(){ let instructors = ["Elie", "Matt", "Tim"]; return { showInstructors: function displayAllInstructors(){ return instructors; }, addInstructor: function addNewInstructor(instructor){ instructors.push(instructor) return instructors; } } } let firstClass = createInstructors(); firstClass.addInstructor("Jennifer"); firstClass.showInstructors(); // ["Elie", "Matt", "Tim", "Jennifer"] let secondClass = createInstructors(); secondClass.addInstructor("Ashley"); // ["Elie", "Matt", "Tim", "Ashley"] // on one line let instructors = createInstructors().showInstructors(); ["Elie", "Matt", "Tim"]
Now that one line we just wrote was pretty neat! let instructors = createInstructors().showInstructors();
, but could we do better? What if we do not want to call createInstructors
every time, so that we could write something like createInstructors.showInstructors()
? To do that, we can use IIFEs!
let instructorModule = (function createInstructors(){ let instructors = ["Elie", "Matt", "Tim"]; return { showInstructors: function displayAllInstructors(){ return instructors; }, addInstructor: function addNewInstructor(instructor){ instructors.push(instructor) return instructors; } } })();
What we have just created is a small module
, which is a piece of code that is encapsulated and can be reused quite easily. The pattern we just used to write our code is famously called the module pattern! It's a great way to wrap everything in an IIFE that contains private data that can not be accessed globally. We can even refactor this more so that our logic is not in the return
statement.
let instructorModuleRefactored = (function createInstructors(){ let instructors = ["Elie", "Matt", "Tim"]; function displayAllInstructors(){ return instructors; } function addNewInstructor(instructor){ instructors.push(instructor); return instructors; } return { showInstructors: displayAllInstructors, addInstructor: addNewInstructor } })();
If you would like to see some more examples on closures take a look at the video below. The slides for this video are here
Every single time that a function is called, we get access to a special keyword called arguments
which looks like an array (it is not EXACTLY an array, but we will cover the reason why a bit later) and can be accessed using []
notation. Here is an example:
function logAll(){ console.log(arguments); } logAll(2,2); // [2,2] logAll(10,5,4); // [10,5,4] logAll(1); // [1] function displayFirstArgument(){ return arguments[0]; } displayFirstArgument(10,20); // [10] displayFirstArgument(true); // [true] displayFirstArgument(); // []
So what do we mean when we say not "exactly" an array? Let's see what happens if we try to use the push
method on the arguments
keyword
function tryPushOnArguments(){ arguments.push("another one"); return arguments; } tryPushOnArguments(); // Uncaught TypeError: arguments.push is not a function
The reason this is happening is because even though the arguments
keyword looks like an array and even has a length
property, it is actually not an array. It is a special kind of object.
Manipulating this arguments
array-like object can be useful, for instance, when you don't know how many arguments someone will pass in to your function:
function add() { let total = 0; for (let i = 0; i < arguments.length; i++) { total += arguments[i]; // this is shorthand for total = total + arguments[i] } return total; } add(1,2,3); // 6 add(1,-2,3,-4); // -2 add(); // 0
Answer the following questions:
arguments
array-like object?Why do we call arguments
an array-like-object
?
Write a function called createCounter
. This function should contain a variable count
that is initialized to 0
. This function should return another function that when invoked, increments the counter by 1 and returns the count variable. You should be able to create multiple counters with the createCounter
function and they should all have their own private variable called count
.
let firstCounter = createCounter(); firstCounter(); // 1 firstCounter(); // 2 firstCounter(); // 3 firstCounter(); // 4 let secondCounter = createCounter(); secondCounter(); // 1 secondCounter(); // 2 secondCounter(); // 3 firstCounter(); // 5 firstCounter(); // 6 secondCounter(); // 4
When you're ready, move on to Higher Order Functions, Timers, and Closures Exercises