Man know thyself: Understanding this keyword in JavaScript

Man know thyself: Understanding this keyword in JavaScript

The this keyword in JavaScript is used as a reference to the scope of the current execution context. What this means is that it represents an environment that contains the code that is currently running and everything that aids in it’s execution. This by default is the global object.(window in browser).

var a = 20
console.log(this) //Window
console.log(this.a) //20 (all variables in th global scope are a property of the global object

However, all functions have their own individual execution context created for them when they are called and the value of this in each function is determined by how the function is called. Note: How it is called, not declared

In non strict mode. The this keyword always represents an object.

This in Functions

For a typical function, the value of this is the object that it is accessed on. i.e the object it is called on as a method. This is known as implicit binding. We use the word ‘binding’ to demonstrate how the JavaScript engine attaches ‘this’ to an object.

function foo(){
    console.log(this.a)
}

const obj = {
    a:2,
    print:foo
}
obj.print() //2

If the function is called without being accessed on anything, this is set to the global this. This is known as default binding and only occurs in non strict mode. In Strict mode it returns undefined

var a = 5
function foo(){
    console.log(this.a)
}

const obj = {
    a:2,
    foo:foo
}
obj.foo() //2
foo() //5 (this resolves to global this)

New Binding

If the function is called as a constructor with the new keyword. this defined inside the function references the newly constructed object. This is the same behaviour for classes as classes behave like constructor functions.


function Foo(a, b){
    this.a = a
    this.b = b
    this.sum = function(){
        return this.a + this.b
    } 
}

const mySum = new Foo(5,7)
console.log(mySum.a)//5
console.log(mySum.b)//7
console.log(mySum.sum())//12

Explicit binding

You can also explicitly set the value of this in a function using some special built in methods that exists on all functions. This makes sure no matter how the function is called, this remains the same.

They are

  • Function.call()

  • Function.apply()

  • Function.bind()

Function.apply and Function.call work very similar in the context of our discussion. They call the function with a given value for this passed as an argument. The function is then executed with the first argument used as this

function sum(){
    console.log(this.a + this.b)
}

const obj1 = {
   a: 8,
   b: 5 
}
const obj2 = {
   a: 2,
   b: 6 
}

sum.call(obj1) //13
sum.call(obj2) //8

Function.bind however takes an argument and returns a copy of the same function that when called has it’s this value bound to the argument.

function sum(){
    console.log(this.a + this.b)
}

const obj1 = {
   a: 8,
   b: 5 
}
const obj2 = {
   a: 2,
   b: 6 
}

let sum1 = sum.bind(obj1) 
sum1() //13
let sum2 = sum.bind(obj2) 
sum2() //8

Conclusion

As we can see there are different ways our JavaScript engine binds this inside a function when said function is being executed. But what happens when there are multiple bindings in a function, how do we determine what this is?

Determining the this binding for an executing function requires finding the direct call-site of that function. Once examined, four rules can be applied to the call-site, in this order of precedence:

  1. Called with new? Use the newly constructed object.

  2. Called with call or apply? Use the specified object.

  3. Called with a context object owning the call? Use that context object.

  4. Default: undefined in Strict Mode, global object otherwise.

this in Arrow functions

With arrow functions there are no binding of this.

In regular functions the this keyword represented the object that called the function, which could be the window, the document, a button or whatever. With arrow functions the this keyword always represents the object that defined the arrow function.

const a = 10
const foo = ()=>{
    console.log(this)
}

let obj1 = {
    a:5,
    foo:foo
}
obj1.foo() //[Window Object] it returns the global window object because foo is defined in the global scope.

Arrow functions are used preferably as callbacks for events, etc to prevent this binding being overridden during code execution.