DEV Community

Brandon C
Brandon C

Posted on

JavaScript's "This" Keyword Simple Intro

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)
}}
Enter fullscreen mode Exit fullscreen mode

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] }
Enter fullscreen mode Exit fullscreen mode

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, …}
Enter fullscreen mode Exit fullscreen mode

Image description: this in chrome console

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.
Image description: this in chrome console with use strict

function whatsThisHere(){
  console.log(this)
};
whatsThisHere()
//<ref *1> Object [global] {
//global: [Circular *1],...
Enter fullscreen mode Exit fullscreen mode
function whatsThisHere(){
  'use strict'
  console.log(this)
};
whatsThisHere()
//undefined
//undefined
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode
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'
Enter fullscreen mode Exit fullscreen mode

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] }
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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'

Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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],
Enter fullscreen mode Exit fullscreen mode

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' ]}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)