Closures and This
Objectives
- Understand and explain Javascript context.
- Understand and explain closures.
- Implement the module pattern in their code.
Review
- Create a function.
- Explain scope.
- Manipulate the DOM with jQuery.
Introduction to Closures and Context
Closures
A closure is an inner function that has access to the outer (enclosing) function’s variables—scope chain.
Scope
A scope in JavaScript defines what variables you have access to. There are two kinds of scope – global scope and local scope.
var a = 1
function foo() {
console.log(a)
}
foo() // 1
Global Scope
If you declare a variable outside of all functions it becomes globally scoped meaning it can be accessed from anywhere within the execution environment. In the example above, foo() logs the value for the global variable a.
function bar() {
var b = 'local value'
console.log(b)
}
bar() // 'local value'
console.log(b) // b is not defined
Local Scope
Variables declared inside of a function are given a local scope and can only be accessed from within the function they were declared. This is why when we run bar() our log of b gives us 'local value' but when we try to access boutside that scope we get b is not defined.
Scope Chains
The scoping rules work the same when there are functions nested inside of other functions; each function gets its own local scope, and variables in that scope can only be accessed from the lines of code within the function they were declared. This is called Lexical Scope.
function someFunc() {
var outerVar = 1;
function zip() {
var innerVar = 2;
}
}
zip has access to both innerVar & outerVar, but someFunc only has access to outerVar
Multiple Nested Scopes
Nesting isn't limited to a single inner scope, there can be multiple nested scopes, each of which adhere to the rule above. With one addition: sibling scopes are also restricted from accessing each other's variables.
function someFunc() {
function zip() {
}
function quux() {
}
}
zip & quux are both inner scopes of someFunc. Just as someFunc cannot access zip's variables, zip cannot access quux's variables (and vice versa)
Scope Tree
function someFunc() {
function zip() {
function foo() {
}
}
function quux() {
}
}
global
│
↓
someFunc()
│
╱ ╲
╱ ╲
↓ ↓
zip() quux()
│
↓
foo()
This code
makes this tree!
Scope Chains
global
↑
│
someFunc()
↑
╱
╱
zip()
↑
│
foo()
Looking from the most inner to the most outer scope forms a Scope Chain:
Closures
global
↑
│
someFunc()
var bar
↑
⋮
Let's say someFunc() declares a variable bar:
global
↑
│
someFunc()
var bar
↑
╱
╱
zip()
alert(bar)
↑
⋮
Given how nesting scope works, it's possible for an inner scope within someFunc() to access bar. In this example, let's say zip() accesses bar
Then zip() is said to Close Over bar. Therefore zip() is a Closure.
global
↑
│
someFunc()
var bar
↑
╱
╱
zip()
alert(bar)
↑
⋮
The closure will be maintained even if zip() isn't executed immediately. It is perfectly legal in Javascript to pass ziparound / return it from someFunc() for later execution. All the while, bar will continue to be available.
This continues down the scope chain. Say zip() declares a variable beep, and foo() alerts it out:
<button>Click Me!</button>
document.addEventListener('DOMContentLoaded', function() {
var count = 0
document.querySelector('button').addEventListener('click', function() {
alert(count)
})
})
The inner function closes over the variable count, and continues to have access to that variable no matter how many times the user clicks on the <button>.
Note that because count is declared inside the outer function, it is not available in the global scope.
<button>Click Me!</button>
document.addEventListener('DOMContentLoaded', function() {
var count = 0
document.querySelector('button').addEventListener('click', function() {
count = count + 1
alert(count)
})
})
We can then extend the code to increase the count each time the button is clicked:
<button id="increase">Increase Number +</button>
<button id="show">Show Me!</button>
document.addEventListener('DOMContentLoaded', function() {
var count = 0
document.querySelector('#increase').addEventListener('click', function() {
count = count + 1
})
document.querySelector('#show').addEventListener('click', function() {
alert(count)
})
})
Because the variable count is in an outer function, and is closed over by the inner function, any modifications made to it anywhere along any of the scope chains leading to it are reflected in every closure:
Create a Closure
Independent Practice
Create a closure that will help you create colored sticky notes dynamically in your DOM with the click of a button.
The CSS is all setup for you in the start index.html, but you will have to add:
- create input and button elements
- run Javascript code only after document is "ready"
- sticky note color and message should both be dictated by user input
- each sticky note message should start with a number representing the order of its creation
Closure Brain Teaser
Independent Practice
function queueCreator(waitList){
var positionInQueue = 1
for (var i=0; i<waitList.length; i++) {
waitList[i].id = function() {
return positionInQueue
}
positionInQueue++
}
return waitList
}
var people = [{name:'George'},{name:'Chris'}]
var queueList = queueCreator(people)
queueList[0].id() // 3?!
As mentioned earlier, the ability for an inner function to reference an outer function variable can be dubious if that variable updates.
function queueCreator(waitList){
var positionInQueue = 1
for (var i=0; i<waitList.length; i++) {
(function(position) {
waitList[i].id = function() {
return position
}
})(positionInQueue) // IIFE
positionInQueue++
}
return waitList
}
var people = [{name:'George'},{name:'Chris'}]
var queueList = queueCreator(people)
queueList[0].id() // 1
Here is a potential solution using immediately invoked function expressions (IIFE):
function queueCreator(waitList){
var positionInQueue = 1
waitList.forEach(function(item, index) {
item.id = function() {
return positionInQueue + index
}
})
return waitList
}
var people = [{name:'George'},{name:'Chris'}]
var queueList = queueCreator(people)
queueList[0].id() // 1
An alternative solution, which also helps explain how the above IIFE works, is to use any one of the built-in array iteration methods, such as forEach:
Here, the scope created by the function passed to .forEach also contains a unique value for index every time it is executed, allowing the closure created by .id to close over the single, unchanging value.
The Module Pattern
var car;
function carFactory(kind) {
var wheelCount, start;
wheelCount = 4;
start = function() {
console.log('started the ' + wheelCount + ' wheel ' + kind + '.');
};
return {
make: kind,
wheels: wheelCount,
startEngine: start
};
}
car = carFactory('Tesla');
// => started the 4 wheel Tesla.
car.startEngine();
Module Pattern
Context
The Meaning and Purpose of this
console.log(this === window) // true
function foo() {
console.log(this === window)
}
foo() // true
foo() === window.foo() // true
If we were to run this single line of code by itself, in the global variable scope, it would run in the context of the window object, and because this refers to the subject of the executing code, the subject in context, its value is equal to the window object it is running within.
By default a function runs within the scope of the object it sits in, so in this case this is still equivalent to the window object.
var chatroomUser = {
age: '22',
sex: 'm',
location: 'Los Angeles',
printASL: function() {
// we can refer to the chatroomUser object with this
console.log(this.age + '/' + this.sex + '/' + this.location)
// or we could refer to it by name
console.log(chatroomUser.age + '/' + chatroomUser.sex + '/' + chatroomUser.location)
}
}
Manipulating Context
var user = {
firstName: 'Chelsea',
lastName: 'Logan',
showFullName: function() {
console.log(this.firstName, this.lastName)
}
}
user.showFullName() // Chelsea Logan
All objects, which includes functions, have properties. And when a function object executes, the value of this is set to the object that invokes said function.
var user = {
firstName: 'Chelsea',
lastName: 'Logan',
showFullName: function() {
console.log(this.firstName, this.lastName)
}
}
$('.button').click(user.showFullName) // undefined undefined
Why would we want to change our context?
When it comes to the use of event handlers, the value of this is not always what we want
Here are the methods that allow us to control context
- call: The call() method calls a function with a given this value and arguments provided individually
- apply: The apply() method calls a function with a given this value and arguments provided as an array (or an array-like object)
- bind: The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called
call:
$('.button').click(function() {
user.showFullName.call(user) // Chelsea Logan
})
apply:
$('.button').click(function() {
user.showFullName.apply(user) // Chelsea Logan
})
var user = {
firstName: 'Chelsea',
lastName: 'Logan',
showFullName: function(one, two, three) {
console.log(this.firstName, this.lastName, one, two, three)
}
}
$('.button').click(function() {
user.showFullName.call(user, 1, 2, 3) // Chelsea Logan 1 2 3
})
$('.button').click(function() {
user.showFullName.apply(user, [1, 2, 3]) // Chelsea Logan 1 2 3
})
var user = {
firstName: 'Chelsea',
lastName: 'Logan',
showFullName: function() {
console.log(this.firstName, this.lastName)
}
}
// declare a new variable whose value is the
// user.showFullName function with a context set to user
var contextSetUser = user.showFullName.bind(user)
$('.button').click(contextSetUser) // Chelsea Logan
$('.button').click(user.showFullName) // undefined undefined
bind is different than call and apply in the sense that it doesn't set the context of a function, but rather bind creates a whole new function with the context you supply it.
This Brainteasers
var fullName = 'John Doe';
var obj = {
fullName: 'Colin Ihrig',
prop: {
fullName: 'Aurelio De Rosa',
getFullName: function() {
return this.fullName;
}
}
};
console.log(obj.prop.getFullName());
var test = obj.prop.getFullName;
console.log(test());
- Explain the results of this code
- Make console.log(test())
return Aurelio De Rosa
Brainteaser solution
-
Context of a function is dependent on how a function is invoked, not how it's defined. For the first log, we execute obj.prop.getFullName() invoking .getFullName() upon the prop object, thereby setting the context of the function to prop and logging prop's fullName property, Aurelio De Rosa. For the second log, .getFullName()is set to the variable test which is in declared in the context of the window object, similar to the first example we saw earlier. Hence, when test is called, the log returns the value of the fullName property of the window object, John Doe.
-
console.log(test.call(obj.prop)) or console.log(test.apply(obj.prop))
Conclusion
- What is the purpose of the module pattern and what are its benefits?
- When is context determined?
- Why would you want to manually change context of a function?
- What is a more commonly used to term for lexical scope?
- Explain one ability closure has upon variable scope that makes it special.
- How does closure work?
Sept 10: Closures and This
By Jessica Bell
Sept 10: Closures and This
- 125