Closure

Background

  • A closure is a function that makes use of variables defined in outer functions that have previously returned
  • Closure does not exist if you do not return an inner function and
  • if that inner function does not make use of variables returned by an outer function

Examples

Example 1

In [3]:
function outer(){
  var start = "Closures are";
  return function inner(){
    return start + " awesome";
  }
}
In [5]:
outer()
// Æ’ inner(){
//   return start + " awesome";
// }

outer()()
Out[5]:
'Closures are awesome'

Example 2

The inner function is making use of the variable "a" which was defined in an outer function called "outer" and by the time inner is called, that outer function has returned this function called "inner" is a closure!

In [8]:
function outer(a){
  return function inner(b){
    return a + b;
  }
}

outer(5)(5);
Out[8]:
10
In [9]:
var storeOuter = outer(5);
storeOuter(10);
Out[9]:
15

Note:

  • We have to 'return' the inner function for this to work
  • We can either call the inner function right away by using an extra () or we can store the result of the function in a variable
  • We do NOT have to give the inner function a name - we can make it anonymous (we just called it "inner" for learning purposes)

Debugger

  • Closures don't remember everything from an outer function - Only variables used in the inner function are remembered!
  • JS will only remember values that are being used inside of the inner function, not all variables defined in the outer function
  • keyword: debugger - pauses execution at the line with the keyword
In [15]:
function outerFn(){
  var data = "something from outer fn";
  var fact = "remember me!";
  return function innerFn(){
  debugger;
    return fact;
  }
}

outerFn()();

// fact; //"remember me!"
// data; //error
Out[15]:
'remember me!'

Private Variable

  • Why do I need to know this?
  • Private Variables - variables that cannot be modified externally.
  • We can use closures to create private variables and write better code that isolates our logic and application
In [17]:
function counter(){
  var count = 0;
  return function inner(){
    count++;
    return count;
  }
}

var counter1 = counter();
counter1; //function definition
counter1();
Out[17]:
1
In [19]:
counter1();
Out[19]:
3
In [21]:
var counter2 = counter();
counter2();
Out[21]:
1
count;
//ReferenceError: count is not defined - because it is private!

Private Variable (more privacy)

In [23]:
function classRoom(){
  var instructors = ["Elie", "Colt"];
  return {
    getInstructors: function(){
      return instructors.slice();
    },
    addInstructor: function(instructor){
      instructors.push(instructor);
      return instructors.slice();
    }
  }
}

var course1 = classRoom();
course1.getInstructors().pop();
Out[23]:
'Colt'
In [24]:
course1.getInstructors().pop();
Out[24]:
'Colt'
In [25]:
course1.getInstructors();
Out[25]:
[ 'Elie', 'Colt' ]
In [27]:
var course2 = classRoom();
course2.getInstructors();
Out[27]:
[ 'Elie', 'Colt' ]

Now the instructors variable is truly private, you're stuck with Colt and Elie for good!

Exercise

Exercise 1

Write a function called specialMultiply which accepts two parameters. If the function is passed both parameters, it should return the product of the two.

If the function is only passed one parameter, it should return a function which can later be passed another parameter to return the product. You will have to use closure and arguments to solve this.

Examples:

specialMultiply(3,4); // 12
specialMultiply(3)(4); // 12
specialMultiply(3); // function(){}....
In [28]:
function specialMultiply(a,b){
  if(arguments.length === 1){
    return function(b){
      return a*b;
    }
  }
  return a*b;
}

Exercise 2

Write a function called guessingGame which takes in one parameter amount. The function should return another function that takes in a parameter called guess. In the outer function, you should create a variable called answer which is the result of a random number between 0 and 10 as well as a variable called guesses which should be set to 0.

In the inner function, if the guess passed in is the same as the random number (defined in the outer function) - you should return the string "You got it!". If the guess is too high return "Your guess is too high!" and if it is too low, return "Your guess is too low!". You should stop the user from guessing if the amount of guesses they have made is greater than the initial amount passed to the outer function.

You will have to make use of closure to solve this problem.

Examples (yours might not be like this, since the answer is random every time):

var game = guessingGame(5)
game(1) // "You're too low!"
game(8) // "You're too high!"
game(5) // "You're too low!"
game(7) // "You got it!"
game(1) // "You are all done playing!"

var game2 = guessingGame(3)
game2(5) // "You're too low!"
game2(3) // "You're too low!"
game2(1) // "No more guesses the answer was 0"
game2(1) // "You are all done playing!"
In [29]:
function guessingGame(amount){
  var answer = Math.floor(Math.random()*11);
  var guesses = 0;
  var completed = false;
  return function(guess){
    if(!completed){
        guesses++
        if(guess === answer) {
            completed = true;
            return "You got it!"
        }
        else if(guesses === amount) {
            completed = true;
            return "No more guesses the answer was " + answer;
        }
        else if(guess > answer) return "Your guess is too high!"
        else if(guess < answer) return "Your guess is too low!"
    }
    return "You are all done playing!"
  }
}

The Keyword 'this'

  • The keyword 'this' is a reserved keyword in JavaScript and its value is determined at execution ('execution context')
  • 4 rules to determine the value of 'this' by execution context:
    • global context
    • object/implicit binding
    • explicit binding (call, apply, bind)
    • new keyword

1 - Global Context

  • When 'this' is NOT inside of a declared object, this will refer to the global object(window if in the browser) or undefined if we are using strict mode
console.log(this); // window

  var instructor = "Elie";
  windows.instructor; //"Elie"
  windows.instructor === instructor; //true
`
  • When 'this' is inside a function, this will have a value of window, the global object
function whatIsThis(){
    return this;
}

whatIsThis(); // window
In [33]:
function variablesInThis(){
    this.person = "Elie"
}

variablesInThis() // undefined (no return in function)
In [34]:
console.log(person);
Elie

Strict Mode

  • We can prevent accidentally creating a global variable by using Strict Mode
"use strict"
console.log(this); // undefined
function whatIsThis(){
  return this;
}
whatIsThis(); // undefined

Since we are in strict mode this is undefined so what happens if we add a property on undefined?

Let's see what happens when we call the function...

"use strict"
function variablesInThis(){
  this.person = "Elie";
}
variablesInThis(); // TypeError, can't set person on undefined!

2 - Implicit/Object Binding

  • When the keyword 'this' is inside of a declared object, the value of the keyword this will always be the closest parent object
// strict mode does NOT make a difference here
var person = {
  firstName: "Elie",
  sayHi: function(){
    return "Hi " + this.firstName;
  },
  determineContext: function(){
    return this === person;
  }
}

person.sayHi(); // "Hi Elie"
person.determineContext(); // true
  • A keyword 'this' is defined when a function is invoked! (tricky)
var person = {
  firstName: "Elie",
  determineContext: this;
}

person.determineContext; // window
  • Nested Object
var person = {
  firstName: "Colt",
  sayHi: function(){
    return "Hi " + this.firstName;
  },
  determineContext: function(){
    return this === person;
  },
  dog: {
    sayHello: function(){
      return "Hello " + this.firstName;
    },
    determineContext: function(){
      return this === person;
    }
  }
}

person.sayHi(); // "Hi Colt"
person.determineContext(); // true

// but what is the value of the keyword this right now?
person.dog.sayHello(); // "Hello undefined" (dog has no firstname property)
person.dog.determineContext(); // false

3 - Explicit Binding

  • Whenever you see the call, apply or bind methods, you can easily determine what the value of the keyword this will be because you get to set it as the first parameter to each of these functions
  • call, apply, bind methods can only be invoked on functions
  • these methods will have precedence over the first two rules
NAME OF METHOD PARAMETERS INVOKE IMMEDIATELY?
Call thisArg, a, b, c, d, ... Yes
Apply thisArg, [a, b, c, d, ...] Yes
Bind thisArg, a, b, c, d, ... No
  • Arguments are passed as comma separated values in call and bind. However, in apply, they are passed as array of values
  • Use apply when a function does not accept an array. Apply will spread out values in an array for us! e.g. spread arr into arr[0], arr[1], arr[2] etc
  • Bind is useful for partial application and when we're working with asynchronous code e.g. with setTimeOut function
    • Partial application: when you don't know all the argements that will be passed to a function, which means we do not want to invoke the function right away
      • (see tricky below)

Example: call

In [41]:
var person = {
  firstName: "Colt",
  sayHi: function(){
    return "Hi " + this.firstName;
  },
  determineContext: function(){
    return this === person;
  },
  dog: {
    sayHello: function(){
      return "Hello " + this.firstName;
    },
    determineContext: function(){
      return this === person;
    }
  }
}

person.dog.sayHello.call(person);
Out[41]:
'Hello Colt'
In [42]:
person.dog.determineContext.call(person);
Out[42]:
true

Using call worked! Notice that we do NOT invoke sayHello or determineContext (no brackets)

Example: refactor with call

Remove duplicate function sayHi

In [44]:
var colt = {
  firstName: "Colt",
  sayHi: function(){
    return "Hi " + this.firstName;
  }
}

var elie = {
  firstName: "Elie"
}

colt.sayHi();
Out[44]:
'Hi Colt'
In [45]:
colt.sayHi.call(elie);
Out[45]:
'Hi Elie'

Example: refactor with call - one step further

Make a sayHi function for everyone

In [46]:
function sayHi(){
  return "Hi " + this.firstName;
}

var colt = {
  firstName: "Colt"
}

var elie = {
  firstName: "Elie"
}

sayHi.call(colt);
Out[46]:
'Hi Colt'
In [47]:
sayHi.call(elie);
Out[47]:
'Hi Elie'

Example: Extract content from a web page (call + filter + slice) Let's imagine we want to select all the 'divs' on a page var divs = document.getElementsByTagName('divs');

How can we find all the divs that have the text "Hello". Using filter would be nice! divs.filter // undefined

Unfortunately, divs is not an array, it's an array like object so filter won't work. So how can we convert an array-like-object into an array? Use slice method on arrays!!

Instead of the target of slice (the keyword this) being that array, let's set the target of the keyword this to be our divs array-like-object.

var divsArray = [].slice.call(divs);
// var divsArray = Array.prototype.slice.call(divs) // (alternative)

divsArray.filter(function(val){
  return val.innerText === 'Hello';
});

What we are doing is trying to slice something that is not actually an array! In JavaScript, slice() will not work on all data types, but it works very well on array-like-objects e.g. string

Example: apply

In the previous example, we can use apply instead of call. It's almost identical to call - except the parameters!

In [50]:
function addNumbers(a,b,c,d){
  return this.firstName + " just calculated " + (a+b+c+d);
}

var colt = {
  firstName: "Colt"
}

var elie = {
  firstName: "Elie"
}

addNumbers.call(elie, 1, 2, 3, 4);
Out[50]:
'Elie just calculated 10'
In [51]:
addNumbers.apply(elie,[1,2,3,4]);
Out[51]:
'Elie just calculated 10'

Use apply when a function does not accept an array. Apply will spread out values in an array for us! e.g. spread arr into arr[0], arr[1], arr[2], ....

In [53]:
var nums = [5,7,1,4,2];
Math.max(nums);
Out[53]:
NaN
In [54]:
Math.max.apply(this, nums);
Out[54]:
7
In [56]:
function sumValues(a,b,c){
    return a+b+c;
}

var values = [4,1,2];
sumValues(values);
Out[56]:
'4,1,2undefinedundefined'
In [57]:
sumValues.apply(this,[4,1,2]);
Out[57]:
7

Example: bind

The parameters work like call, but bind returns a function with the context of this bound already!

In [60]:
function addNumbers(a,b,c,d){
    return this.firstName + " just calculated " + (a+b+c+d);
}
var elie = {
    firstName: "Elie"
}

var elieCalc = addNumbers.bind(elie,1,2,3,4); // bind return a function(){}...

elieCalc(); //note: the parenthesis is added to invoke function
Out[60]:
'Elie just calculated 10'

With bind - we do not need to know all the arguments up front!

In [62]:
var elieCalc = addNumbers.bind(elie,1,2);
elieCalc(3,4);
Out[62]:
'Elie just calculated 10'

Example (tricky) : What does this refer to?

In [64]:
var colt = {
  firstName: "Colt",
  sayHi: function(){
    setTimeout(function(){
      console.log("Hi " + this.firstName);
    },1000);
  }
}

colt.sayHi();
Hi undefined

Ticky!!

  • Since the setTimeOut is called at a later point in time, the keyword this does not refer to the parent object i.e. colt.
  • It actually refer to the object that it is attached to i.e. window! Remeber setTimeOut is a method of the window object.
  • As we loose the context of the keyword this with setTimeout, we should explicitly set what we want the keyword this to refer to.
  • Use bind to set the correct context of 'this'

1st this refer to window, 2nd this refer to colt

In [65]:
var colt = {
  firstName: "Colt",
  sayHi: function(){
    setTimeout(function(){
      console.log("Hi " + this.firstName);
    }.bind(this),1000);
  }
}

colt.sayHi(); // 1000 milliseconds later
Hi Colt

4 - The keyword new

  • When you see the new keyword is used with the function, the keyword this will refer to the new object that is created
  • When the new keyword is used, an implicit return this is added to the function which uses it
  • The keyword new will be discussed further when we take about OOP
In [67]:
function Person(firstName, lastName){
    this.firstName = firstName; //this refer to global object here i.e. window
    this.lastName = lastName;
}

var elie = new Person("Elie", "Schoppik");
elie.firstName;
Out[67]:
'Elie'
In [68]:
elie.lastName;
Out[68]:
'Schoppik'