DEV Community

Aditya Tripathi
Aditya Tripathi

Posted on • Edited on

Javascript "this" binding with examples

In this article, we will explore a runtime construct for JavaScript language called this. Compared to other languages, this behaves differently in JavaScript.

this keyword is a way to implicitly pass information of an execution context to running execution context. If you are not sure what those terms mean, checkout out my other articles on JavaScript Runtime and JavaScript Scopes in-depth.

It is a way to provide data implicitly between different scopes.

The value of this depends on which object it is bound to in runtime. This binding is decided using a set of rules which are always decided on the function call-site, and not where function is declared.

Let us try to understand this with a very simple example:

var me = {name: 'Writer'}
var you = {name: 'Reader'}

function printNameImplicit(){. // declaration site
    console.log(this.name)
}

printNameImplicit.call(me) // call-site 1
printNameImplicit.call(you) // call-site 2

Enter fullscreen mode Exit fullscreen mode

Using call (more on it later), we bind this of printNameImplicit function (or of the scope created by the function) to any object we want. this value then becomes that object, and name is displayed accordingly.

In a way, we "imported" me and you object in printNameImplicit function's scope at runtime.

Consider another example:

var person = {name: 'Adam'}

function addAge(age){
 this.age = age;
}

function addAddress(address){
 this.place = address
}

addAge.call(person,20);
addAddress.call(person,'House 1, Street A')

console.log(person) //{ name: 'Adam', age: 20, place: 'House 1, Street A' }

Enter fullscreen mode Exit fullscreen mode

Using this we can build up an object or modify its existing properties.

Bindings

As we have seen above, call helps us bind this of a function to an object. It takes two parameters, the first being the object we need this to be bound to, the second is the argument required by the function whose this is being bound.

But what happens when we have multiple arguments for the function? We can pass subsequent parameters to call or
we can use a very similar construct called apply. It is almost identical to call except that its second parameter is an array of values, representing multiple parameters of the function. If we modify our above example, by combining the functions:

var person = {name: 'Adam'}

function addAddressAndAge(address, age){
 this.place = address
 this.age = age;
}

addAddressAndAge.apply(person, ['House 1, Street A', 20]);
// or addAddressAndAge.call(person, 'House 1, Street A', 20);

console.log(person) // { name: 'Adam', age: 20, place: 'House 1, Street A' }

Enter fullscreen mode Exit fullscreen mode

As mentioned above call and apply are methods are a way to bind this to an object. By default, in non-strict mode, this is bound to the Global Object. In strict mode, this is bound to nothing and hence its value is undefined. This scenario is called as default binding.

// non-strict mode
var a = 10;

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

print() // 10
Enter fullscreen mode Exit fullscreen mode

In the above example, this is bound to global scope. In strict mode, the result would be undefined instead.

Implicit Binding

Implicit binding refers to this being bound to the object decorated on the call-site of the function. Consider the following example:


function printName(){
  console.log(this.name)
}

const person = {
  name: 'Adam',
  printName: printName // implicit 'this' binding
}

person.printName();  // call site decorated with person object

Enter fullscreen mode Exit fullscreen mode

When we modify object person to include printName inside it, the this of printName is implicitly bound to person if the function call is made using the person object.

Also note that only the closest object reference at call site is bound to this. For eg. if we do:

function printName(){
  console.log(this.name)
}

const person = {
  name: 'Adam',
  printName: printName // implicit 'this' binding
}

const anotherPerson = {
  name: 'Eve',
  friend: person
}

anotherPerson.friend.printName(); // Adam

Enter fullscreen mode Exit fullscreen mode

friend, i.e. person object will be bound to 'this' because it is closer to printName function call.

Implicit bindings are lost when the call-site is undecorated. When implicit bindings are lost, they fallback to default binding.

let name = 'Eve'

function printName(){
  console.log(this.name)
}

const person = {
  name: 'Adam',
  printName: printName // implicit 'this' binding
}

printName() // Eve, in non-strict mode, undefined in strict

Enter fullscreen mode Exit fullscreen mode

Implicit bindings are also easily lost or re-bound in-case of assignments and callbacks.

let name = 'Eve'

function printName(){
  console.log(this.name)
}

const person = {
  name: 'Adam',
  printName: printName
}

setTimeout(person.printName, 1000) // Eve, implicit binding lost

Enter fullscreen mode Exit fullscreen mode

In other words it do not automatically forms a closure with the bound object.

Explicit Binding

Explicit binding, as the name suggests, is when we explicitly bind this to an object using constructs like call, apply or bind which we saw above. Unlike implicit binding, explicit binding constructs allow us to bind this without actually altering the object its bound to.


function printName(){
  console.log(this.name)
}

const person = {
  name: 'Adam',
  age: '32'
}

printName.call(person); // Adam
printName.apply(person); // Adam

Enter fullscreen mode Exit fullscreen mode

However, explicit binding does not cure the problem of losing our bindings and is fragile like implicit binding. To tackle this, we can perform a certain type of explicit binding called as hard explicit binding or just hard binding.

Hard binding wraps the call-site in a wrapper function, this makes sure that the scope in which a function's this is bound to the target object is not influenced by other scope.


function printName(){
  console.log(this.name)
}

const person = {
  name: 'Adam',
  age: '32'
}

let hardBind = function (targetFunction, targetObject){
  targetFunction.call(targetObject)
}

setTimeout(() => hardBind(printName, person), 1000); // Adam

Enter fullscreen mode Exit fullscreen mode

Here, we provide a wrapping function, so that our calling code (which is setTimeout) is forced to perform binding through that wrapper function only, making sure that whenever printName is called, its this is bound to the personObject.

The above example utilises Closures. If you do not understand how? Take a look at my article on closure: JavaScript Closures with examples

The wrapper function is a common binding design pattern in JavaScrip and hence it is supplied as a default construct by the language in the name of bind. The bind returns a function which is called every-time we want to perform hard binding.


function printName(){
  console.log(this.name)
}

const person = {
  name: 'Adam',
  age: '32'
}

let hardBind = printName.bind(person)


setTimeout(() => hardBind(printName, person), 1000); // Adam

Enter fullscreen mode Exit fullscreen mode

new Binding

To understand this binding created by new operator, let us quickly refresh how new works.

Calling any function with new makes it a constructor call. A constructor calls returns a newly created object, unless the function being called returns its own object.

The new operator binds the constructor function's this to this newly created object.


function Person(name, age){
  this.name = name;
  this.age = age;
}

let person = new Person('Adam', 32)

console.log(person) // Person { name: 'Adam', age: 32 }

Enter fullscreen mode Exit fullscreen mode

Arrow Functions

this behaves differently from arrow functions. Unlike regular functions arrow functions do not have their own this, they form a closure with the this value of enclosing scope. This behaviour is sometimes useful when we do not want to shadow this value by creating our own for each function declared.

const obj = {
 // implicitly bound this  
 getThisGetter() {
    const getter = () => this;
    return getter;
  },
};


const fn = obj.getThisGetter();
console.log(fn() === obj); // true

const fn2 = obj.getThisGetter;
console.log(fn2()() === globalThis); // true in non-strict mode

Enter fullscreen mode Exit fullscreen mode

this keyword in JavaScript plays a crucial role in determining the runtime binding of functions to objects. Unlike many other programming languages, this in JavaScript is dynamic and relies on the rules set during the function call-site, not the declaration. We explored how this can be explicitly bound using methods like call, apply, and bind, allowing for flexibility in managing the context in which functions are executed.

That's all for now. Until next time :)

Top comments (0)