function outer(){
var start = "Closures are";
return function inner(){
return start + " awesome";
}
}
outer()
// Æ’ inner(){
// return start + " awesome";
// }
outer()()
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!
function outer(a){
return function inner(b){
return a + b;
}
}
outer(5)(5);
var storeOuter = outer(5);
storeOuter(10);
Note:
debugger
- pauses execution at the line with the keywordfunction outerFn(){
var data = "something from outer fn";
var fact = "remember me!";
return function innerFn(){
debugger;
return fact;
}
}
outerFn()();
// fact; //"remember me!"
// data; //error
function counter(){
var count = 0;
return function inner(){
count++;
return count;
}
}
var counter1 = counter();
counter1; //function definition
counter1();
counter1();
var counter2 = counter();
counter2();
count;
//ReferenceError: count is not defined - because it is private!
slice()
to make variable truely private!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();
course1.getInstructors().pop();
course1.getInstructors();
var course2 = classRoom();
course2.getInstructors();
Now the instructors variable is truly private, you're stuck with Colt and Elie for good!
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(){}....
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!"
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!"
}
}
console.log(this); // window
var instructor = "Elie";
windows.instructor; //"Elie"
windows.instructor === instructor; //true
`
function whatIsThis(){
return this;
}
whatIsThis(); // window
function variablesInThis(){
this.person = "Elie"
}
variablesInThis() // undefined (no return in function)
console.log(person);
"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!
// 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
var person = {
firstName: "Elie",
determineContext: this;
}
person.determineContext; // window
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
call
, apply
, bind
methods can only be invoked on functionsNAME 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 |
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] etcBind
is useful for partial application and when we're working with asynchronous code e.g. with setTimeOut functionExample: call
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);
person.dog.determineContext.call(person);
Using call worked! Notice that we do NOT invoke sayHello or determineContext (no brackets)
Example: refactor with call
Remove duplicate function sayHi
var colt = {
firstName: "Colt",
sayHi: function(){
return "Hi " + this.firstName;
}
}
var elie = {
firstName: "Elie"
}
colt.sayHi();
colt.sayHi.call(elie);
Example: refactor with call - one step further
Make a sayHi function for everyone
function sayHi(){
return "Hi " + this.firstName;
}
var colt = {
firstName: "Colt"
}
var elie = {
firstName: "Elie"
}
sayHi.call(colt);
sayHi.call(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!
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);
addNumbers.apply(elie,[1,2,3,4]);
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], ....
var nums = [5,7,1,4,2];
Math.max(nums);
Math.max.apply(this, nums);
function sumValues(a,b,c){
return a+b+c;
}
var values = [4,1,2];
sumValues(values);
sumValues.apply(this,[4,1,2]);
Example: bind
The parameters work like call, but bind returns a function with the context of this
bound already!
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
With bind
- we do not need to know all the arguments up front!
var elieCalc = addNumbers.bind(elie,1,2);
elieCalc(3,4);
Example (tricky) : What does this refer to?
var colt = {
firstName: "Colt",
sayHi: function(){
setTimeout(function(){
console.log("Hi " + this.firstName);
},1000);
}
}
colt.sayHi();
Ticky!!
setTimeOut
is called at a later point in time, the keyword this
does not refer to the parent object i.e. colt.setTimeOut
is a method of the window
object.this
with setTimeout
, we should explicitly set what we want the keyword this
to refer to.bind
to set the correct context of 'this'1st this refer to window, 2nd this refer to colt
var colt = {
firstName: "Colt",
sayHi: function(){
setTimeout(function(){
console.log("Hi " + this.firstName);
}.bind(this),1000);
}
}
colt.sayHi(); // 1000 milliseconds later
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;
elie.lastName;