- Explain Javascript 'context' and what the value of the 'this' keyword refers to
- Explain what the default context of Javascript executing in the browser is
- Use the 'this' keyword to set and retrieve a property in a Javascript function
- Use bind to create a new method bound to an object context
- Use apply/call to execute a method in a different context
In Javascript, context tells us where functions are invoked.
In short, the context is the object that a function is attached to. We'll see that context can change under certain circumstances.
Every time a Javascript function is called, a context is determined / set. That context is always an object, and can be referenced in the function definition (code) using a special keyword in JS, this.
We use this similar to the way we use pronouns in natural languages like English and French. Say we write:
“John is running fast because he is trying to catch the train.”
We could have written this:
“John is running fast because John is trying to catch the train.”
In a similar manner, we use the this keyword as a replacement for the subject in question.
Here's an example of the most common way context is determined for a function. When a method is called on an object, that object becomes the context...
var user = {
firstName: "John",
sayName: function(){
alert("My name is " + this.firstName + ".");
}
}
user.sayName();What does this represent here?
What's to the left of the period when
sayNameis called isuser. So,this === user.
In general, this is whatever was to the left of the period when it was called, unless...
- You're in an event listener function, in which case
thisis the thing that was clicked on. - You're in another callback function, in which case
thisis probably theWindow.
If you're ever unsure what this is at a given point in your code.
console.log(this)var instructor = {
fullName: "Nayana Davis",
favoriteFood: "Fried Chicken",
sayHello: function(){
console.log(`Hi my name is ${this.fullName} and my favorite food is ${this.favoriteFood}`)
}
}
instructor.sayHello(); // for this function invocation, `this` is `instructor`This feature allows not just 'getting' property info on objects, but also setting properties. Consider this example:
var user = {
userName: "AndyWhitley",
isSignedIn: false,
signIn: function() {
this.isSignedIn = true
},
signOut: function() {
this.isSignedIn = false
}
}
user.signIn()
user.isSignedIn // => true
user.signOut()
user.isSignedIn // => falseBut what if we want more control?
Because we've written a method to set the isSignedIn property, we can use that method to provide more control. For example... what if we wanted to check a user's password before letting them sign in?
var user = {
userName: "AndyWhitley",
password: "password1234",
isSignedIn: false,
signIn: function(pwd) {
if(pwd === this.password) {
this.isSignedIn = true
}
},
signOut: function() {
this.isSignedIn = false
}
}
user.signIn("tacobell")
user.isSignedIn // => false
user.signIn("password1234")
user.isSignedIn // => true
user.signOut()
user.isSignedIn // => falseWe can also use this to reference and call other methods on the object.
var user = {
userName: "AndyWhitley",
password: "password1234",
isSignedIn: false,
signIn: function(pwd) {
if(pwd === this.password) {
this.isSignedIn = true
this.greetUser()
}
},
signOut: function() {
this.isSignedIn = false
},
greetUser: function() {
console.log("Welcome back " + this.userName)
}
}
user.signIn("tacobell")
user.isSignedIn // => false
user.signIn("password1234")
// => Welcome back AndyWhitley
user.isSignedIn // => true
user.signOut()
user.isSignedIn // => false
-<button>Hi there</button>$("button").on("click", function(){
alert($(this).html());
});What does this represent here?
this ===the button, so it alerts "Hi there".
When a function is called, but it's not a method on an object, and no context is otherwise assigned (see later sections), then the context is set to the default context. In a browser, the default context is the window object.
function revealThis() {
console.log(this);
}
revealThis();var fruits = ["apple", "banana", "cantaloupe"];
fruits.forEach(function(){
// this === the `Window` object
});forEach is a Javascript method used to iterate through a collection and do something with each item in it.
var fruits = ["apples", "bananas", "cherries"];
for(var i = 0; i < fruits.length; i++) {
console.log("Every day I eat two " + fruits[i]);
}
fruits.forEach(function(currentFruit) {
console.log("Every day I eat two " + currentFruit)
});Note that it is very rare to intentionally use this to refer to the window object. Usually this happens when we mistakenly use this incorrectly (a very easy/common mistake for new and even experienced JS developers).
Consider the following example...
var instructor = {
fullName: "Angel Valant",
favoriteFoods: ["Ramen", "Capn Crunch", "Tacos"],
displayFoods: function() {
console.log("Things " + this.fullName + " likes:")
this.favoriteFoods.forEach(function(food) {
console.log(food);
})
}
}
instructor.displayFoods();Using what we know about forEach, what do we expect the output to be?
Now what about this slightly modified example...
var instructor = {
fullName: "Angel Valant",
favoriteFoods: ["Ramen", "Capn Crunch", "Tacos"],
displayFoods: function() {
this.favoriteFoods.forEach(function(food) {
console.log(this.fullName + " likes " + food);
})
}
}
instructor.displayFoods();In the first case, this behaves like we would expect. It references instructor since it's inside a function attached to an instructor.
In the second case, this is inside an anonymous function, so it refers to the global object.
Note that this issue frequently appears anytime we use a callback / anonymous function, such as...
- using
setTimeout()orsetInterval()to schedule callbacks - using
forEach()or other iteration functions - for event listeners passed into
someElement.addEventListener()
One trick is to store the this you want in another variable, commonly named self or that.
var instructor = {
fullName: "Angel Valant",
favoriteFoods: ["Ramen", "Cap'n Crunch", "Tacos"],
displayFoods: function() {
var self = this;
this.favoriteFoods.forEach(function(food) {
console.log(self.fullName + " likes " + food);
})
}
}
instructor.displayFoods();10 minutes exercise. 5 minutes review.
/*A*/
var user = {
firstName: "john",
capitalized: function(){
/*B*/
return this.firstName.substring(0,1).toUpperCase() + this.firstName.substring(1);
},
sayName: function(){
/*C*/
alert("My name is " + this.capitalized() + ".");
}
}
console.log("Welcome, " + user.capitalized() + "!");
$("button").on("click", user.sayName);
$("input").on("keydown", function(){
/*D*/
console.log("Keypress detected for " + this.firstName);
});
user.sayName();
/*E*/When the code above is executed...
- What is the value of
thisat A?Windownulluser$(jQuery)
- What is the value of
thisat B?Windownulluser$(jQuery)
- Why does the click event throw an error?
- Because there aren't parentheses after
user.sayName - Because
user.sayNameis in an event sothisis notuser - Because you can't use
alertinside a function - Because
useris not accessible in the scope of the event listener
- Because there aren't parentheses after
- What is the value of
thisat D?- The
keydownevent Window- The element that was keyed-down upon
user
- The
- Why does the
user.sayName()at the end not throw an error?- Because
thisisuser: what was to the left of the period - Because
userdidn't exist until it was created with the click event - Because it is called in the global scope
- Because
When you've finished...
- `Window`
- `user`
- Because `user.sayName` is in an event so `this` is not `user`. In an event listener `this` is always the HTML element that triggered the event.
- The `input` that was keyed-down upon
- Because `this` is `user`: what was to the left of the period
Read through the bonus section of this lesson plan, paying attention to the bind, call and apply methods. These are ways for you to exercise more control over and gain the ability to re-assign context.
Also take a look at this repo, which compares good and bad ways to apply context. We suggest reading up on bind, call and apply before doing so, however, since the examples make use of some of these methods.
Often we have multiple pieces of data in our program that share the same structure. Think flash cards, trivia cards, bank accounts, etc.
In the future, we'll make these objects using "constructors" (think templates for each type), but then we need a way to talk about the structure in general. Context is a very necessary tool to accomplish this.
An example of what this might look like:
There are two other ways to invoke a function and change the context, which are very similar: bind, call, and apply.
These let you "force" this to be something specific.
var instructor = {
name: "Angel Valant",
favoriteFoods: ["Ramen", "Cap'n Crunch", "Tacos"],
displayFoods: function() {
this.favoriteFoods.forEach(function(food) {
console.log(this.name + " likes " + food);
}.bind(this))
}
}
instructor.displayFoods();function sayHello() {
console.log("Hi! My name is " + this.name);
}
var person = {name: "Manatee the Railyard Toreador"};
var cat = {name: "Hobbles McGillicudy"};
sayHello.call(person);
sayHello.call(cat);call also lets us pass in the arguments to the function:
function sayHello(favColor) {
console.log("Hi! My name is " + this.name + " and I like " + favColor);
}
var person = {name: "Manatee the Railyard Toreador"};
var cat = {name: "Hobbles McGillicudy"};
sayHello.call(person, "blue");
sayHello.call(cat, "peachpuff");apply works almost exactly like call, only you pass in array of arguments instead of a comma-separated list.
apply is useful when the number of arguments to pass to the function is unknown and/or arbitrary.
Note that #1 is included here for correctness, we haven't covered object constructors yet, but will soon.
Is the function called with
new(new binding)? If so,thisis the newly constructed object.var supremePizza = new Pizza()Is the function called with
callorapply(explicit binding), even hidden inside abindhard binding? If so,thisis the explicitly specified object.var bakedPizza = bake.call( rawPizza )Is the function called with a context (implicit binding), otherwise known as an owning or containing object? If so,
thisis that context object.var bakedPizza = rawPizza.bake()Otherwise, default the
this(default binding). If instrict mode, pickundefined, otherwise pick theglobalobject.var probablyWontWork = bake()Source: You-Dont-Know-JS/ch2.md