For JavaScript, this
as part of a function is a tricky keyword because it behaves differently due to the implicit context of the function being called. As part of an object, a short answer for describing this
is that its a keyword in a function that uses the object that the function is contained in as its value. With objects containing functions as properties, these functions are methods of the object and when executed use the object as its execution context. As an example, say we have an object called obje
and one of the keys of obje
is num
with a value of "123" while the second key is str
with 'abc' and the third is a function called func
whose value is a function that contains console.log(this)
.
const obje = {
num: 123,
str: 'abc',
func: function(){
console.log(this)
}}
When you call obje.func
with ()
, what results is the console logs the contents of obje
in its entirety.
obje.func()
//{ num: 123, str:'abc', func: [Function: func] }
With the example provided, an easy way to describe this
is that its value is typically the containing object to the left of the method being called. In the global context when a function containing this
is called, the value of this
will be the global object which in browsers is also usually called the window
.
console.log(this)
Window {0: Window, window: Window, self: Window, document: document, name: '', location: Location, …}
The window
contains all the things like functions, variables, and objects that the user has access to both visible such as declared by the user and provided from behind the scenes such as built in JavaScript methods. To prevent this
from using the global object as its value, we can use use strict
to change its value to undefined in a function.
function whatsThisHere(){
console.log(this)
};
whatsThisHere()
//<ref *1> Object [global] {
//global: [Circular *1],...
function whatsThisHere(){
'use strict'
console.log(this)
};
whatsThisHere()
//undefined
//undefined
There are three methods commonly used for functions utilizing this
which are call
, apply
and bind
. These methods are useful because ordinarily when functions using this
are called in the global context or a context that doesn't use the containing object, the value of this
no longer points to the object the function was originally in and usually results in an error when the function is executed.
const obje = {
num: 123,
str: 'abc',
func: function(key, key2){
return `Here is ${this[key]} and ${this[key2]}`};
}}
obje.func('num', 'str')
//'Here is 123 and abc'
const globFunc = obje.func
globFunc('num', 'str')
//'Here is undefined and undefined'
const obje = {
num: 123,
str: 'abc',
nestFunc: {
innerFunc: function(key, key2){
return `Here is ${this[key]} and ${this[key2]}`}
}
};
obje.nestFunc.innerFunc('num','str')
//'Here is undefined and undefined'
By using the three methods, we can explicitly set the value of this
but they can also be used to help abstract functions that are already not part of an object.
function example(){return this}
example.call(obje)
//or
example.apply(obje)
//{ num: 123, str: 'abc', func: [Function: func] }
call
can be used to execute a function containing this
with the argument parameters allowing the object value of this
to be set as the first argument in call
and the rest of the function arguments to be passed after the first argument.
const obje = {
num: 123,
str: 'abc',
};
const globFunc = function(key, key2){
return `Here is ${this[key]} and ${this[key2]}`};
const globNum = 'num';
const globStr = 'str';
globFunc.call(obje, globNum, globStr)
//or
globFunc.call(obje, 'num', 'str')
//'Here is 123 and abc'
apply
works like call
with the main difference being that the arguments for the function parameters after setting this
is provided as an array instead of separate arguments being written individually.
const exArray = ['num','str']
globFunc.apply(obje, exArray)
//or
globFunc.apply(obje, ['num','str'])
//'Here is 123 and abc'
call
and apply
are similar in that they execute the the function immediately with a set parameter for this
and additional arguments are passed as part of the function. The bind
method can be used to set the context object for this
where the object is passed as an argument, but behaves differently.
bind
is used for assigning this
a value though a function expression declaration to create a separate function with a set object 'bound' to the function. There's no need to add additional arguments for using bind
other than the object unless you want to have an argument permanently attached to the new function with the object. The strength of bind
allows us to create a copy of the function with this
bound to the object
value chosen, while adding additional arguments as with call
and apply
will become the default argument and it can't be changed later. Unlike call
and apply
, bind
isn't executed immediately.
const obje = {
num: 123,
str: 'abc',
func: function(key){
return `Here is ${this[key]}`};
}}
const bindFunc = obje.func.bind(obje);
bindFunc('num')
//'Here is 123'
bindFunc('str')
//'Here is abc'
const bindFuncWithArg = obje.func.bind(obje, 'num')
bindFuncWithArg('str')
//'Here is 123'
bindFuncWithArg()
//'Here is 123'
In our assignments in Flatiron, we learned about the "lost context bug", which occurs when this
inside a function loses its context and it can be due to being a nested function that does not share the same context as the object being referenced.
const obje = {
fruitColors: {
apple: "red",
banana: 'yellow',
orange: "orange",
},
fruits: ['apple','banana','orange'],
};
function whatColorFruit(fruit){
console.log(`The ${fruit} is ${this.fruitColors[fruit]}`)
};
whatColorFruit.call(obje, 'apple')
//The apple is red
function whatColorAll(){
const whatColor = function(fruit){
console.log(`The ${fruit} is ${this.fruitColors[fruit]}`)};
this.fruits.forEach(whatColor);
};
whatColorAll.call(obje)
//TypeError: Cannot read properties of undefined (reading 'apple')
There are different ways to deal with the lost context situation, one technique to remedy this bug is to use bind
to explicitly set the context of this
on the nested function to be used for the forEach
method.
function whatColorAll(){
const whatColor = function(fruit){
console.log(`The ${fruit} is ${this.fruitColors[fruit]}`)
}.bind(this);
this.fruits.forEach(whatColor);
};
whatColorAll.call(obje)
//The apple is red
//The banana is yellow
//The orange is orange
or
function whatColor(fruit){
console.log(`The ${fruit} is ${this.fruitColors[fruit]}`)};
function whatColorAll(){
const bindColor = whatColor.bind(this)
this.fruits.forEach(bindColor);
};
whatColorAll.call(obje)
//The apple is red
//The banana is yellow
//The orange is orange
Here we use call
to set the value of this
to obje
, and bind
creates a new function with the value of this
set to obje
(taken from call
) so that the value of this
doesn't get lost when put into the forEach
method.
Another way is to manually set the context by saving the value of this
to a variable in the outer scope and use that variable in the nested function instead of this
so we can point to the object being used without losing the value of this
in the nested function context.
function whatColorAll(){
const self = this;
const whatColor = function(fruit){
console.log(`The ${fruit} is ${self.fruitColors[fruit]}`)};
this.fruits.forEach(whatColor);
};
whatColorAll.call(obje)
//The apple is red
//The banana is yellow
//The orange is orange
A third simple solution is to use an arrow function in the nested function instead of a function expression since arrow functions use the context of the parent function instead of creating another context. This means we don't have to manually set the value of this
like with the other two options.
function whatColorAll(){
const whatColor = fruit => {
console.log(`The ${fruit} is ${this.fruitColors[fruit]}`)};
this.fruits.forEach(whatColor);
};
whatColorAll.call(obje)
//The apple is red
//The banana is yellow
//The orange is orange
Conclusion
The keyword this
is a little strange in the way it behaves, especially since it has the potential to be something different based on the context of how the function is called. One way to keep track of its value is simply to console.log the value of this
to see what object is being used.
function whatsThisEach(){
const whatsThis = function(fruit){
console.log(this)
};
this.fruits.forEach(whatsThis);
};
whatsThisEach.call(obje)
//<ref *1> Object [global] {
//global: [Circular *1],
Here we see that the value of this
is the global object, which helps explain why it didn't work earlier if we didn't set the value of this
or use the arrow function.
function whatsThisEach(){
const whatsThis = fruit => {
console.log(this)
};
this.fruits.forEach(whatsThis);
};
whatsThisEach.call(obje)
//{fruitColors: { apple: 'red', banana: 'yellow', orange: 'orange'}, fruits: [ 'apple', 'banana', 'orange' ]}
//{fruitColors: { apple: 'red', banana: 'yellow', orange: 'orange' }, fruits: [ 'apple', 'banana', 'orange' ]}
//{fruitColors: { apple: 'red', banana: 'yellow', orange: 'orange' }, fruits: [ 'apple', 'banana', 'orange' ]}
Having the knowledge of how it behaves and how we can use call
apply
or bind
to help set the value of this
allows us to utilize a tool that helps abstract functions by make them more applicable for working with different objects across different execution contexts.
Resources
- this - JavaScript | MDN (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)
- Function.prototype.call() - JavaScript | MDN (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call)
- Function.prototype.apply() - JavaScript | MDN (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply)
- Function.prototype.bind() - JavaScript | MDN (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)
- Flatiron School (https://flatironschool.com/)
Top comments (0)