The 'this' keyword: binding and context

Anton Ioffe - August 30th 2023 - 16 minutes read

Understanding the 'this' keyword in JavaScript/TypeScript

The 'this' keyword in JavaScript and TypeScript is a fundamental concept that is often misunderstood by developers. A simple skim through code and stumbling upon 'this', can turn into a moment of distraction due to the intricacies involved in fully understanding it. Overcoming this predicament requires a meticulous understanding of 'this', its purpose, and the contexts in which it operates.

Understanding the 'this' keyword

In JavaScript or TypeScript, this is a special keyword automatically defined in every execution context (global, function, or eval). The value of this is determined during the execution of a function, based on the context the function is called - a concept known as "runtime binding". Here's an illustration:

// 'this' in a function context
function logThis() {
    console.log(this);
}
logThis();  //The console will log the global window object (in a browser context)

At its core, 'this' refers to the object that a function is a method of. If the function is invoked as a regular function and not as a method invocation, then this points to the global object (often window in a browser context).

Why do developers avoid this?

The use of this is often avoided because the value of this can change unexpectedly based on the context of the function invocation, leading to unpredictable behavior.

A simple way to counteract this unpredictability is by using bind(), call(), or apply(). These methods allow you to control the context of this.

'this' in the ES6 Context

ES6 introduces more complexity for the 'this' keyword with arrow functions. Unlike normal functions, this inside arrow functions is not based on how they are invoked. Instead, the value of this is determined by the surrounding lexical context. Here's an example:

const myObject = {
    myMethod() {
        console.log(this);  // 'this' refers to myObject
        const innerFunction = () => {
            console.log(this);  // 'this' also refers to myObject in arrow functions
        };
        innerFunction();
    }
};
myObject.myMethod();  

In this instance, arrow functions tie the value of this to the surrounding context at creation time and do not alter it at run time. This makes this predictable and helps to avoid potential pitfalls.

Common Blunders

A common mistake is using this in event handlers. Here’s an example:

$('button').click(function(){
    console.log(this);   // Here, 'this' refers to the DOM element clicked
    setTimeout(function(){
        console.log(this);   // Here, 'this' refers to the global window object
    },1000);
});

In this case, the value of this inside the setTimeout function differs from the preceding 'this', shifting to represent the window object instead. This shift often catches developers off guard.

With a clear understanding of the context in which your function is placed and invoked, you can navigate the complexities of 'this'. Just remember, 'this' is not about where a function is declared, but how it is invoked.

Context in JavaScript/TypeScript

Diving into the vast world of JavaScript and TypeScript, one of the core concepts we encounter is the context. The understanding and manipulation of context bear considerable influence on how functions and variables interact, controlling their accessibility and visibility.

In JavaScript, the arrival of function invocation sets the stage for an execution context. This abstract construct, encapsulating the condition under which the existing code is put to evaluation, can be broadly classified into two types:

  1. Global Context: This context blankets the code during its execution phase. It constitutes the base context that remains accessible universally in your JavaScript code.

  2. Function Context: This context steps into the picture each time a function is initiated. It defines the entire settings and data accessible to the function, along with its specific 'this' value.

Before delving deeper, let's unearth the key differences between the concept of context and the keyword ‘this’. Notably, the keyword 'this' is a critical component of the context. Its value gets updated on each context change. Simply put, the keyword 'this' is context-driven. It is defined by the nature of function calls rather than its location within an object or a method.

To illustrate, in the realm of regular function calls and method calls, 'this' displays varying references:

function showThis() {
    console.log(this);
}

showThis(); // 'this' points to the global object (window in a browser)

let obj = {
    showThis: showThis
};

obj.showThis(); // 'this' points to the object containing 'showThis'. In this case, 'obj'

Context within a function acts as a powerful tool, assigning the function a particular perspective of the data available in global and parent scopes.

Creating a context in JavaScript is simpler than it seems. Consider it as an object with properties referring to the 'this' value, function references, and a pointer to the parent context, as shown:

function createContext(functionReference) {
    return {
        'this': window,
        function: functionReference,
        parentContext: window
    } 
}

var myFunc = function() { console.log(this) };
createContext(myFunc); // Constructs a new context with 'this' mapped to window

Scope and context may seem interchangeable due to their focus on code handling, but they differ in execution. Scope revolves around the visibility or accessibility of variables, functions, and objects during runtime, while context concerns the object within which a function is run.

Efficient use of context within functions in JavaScript can be exemplified in event handlers, where the event object is routinely passed as an argument to the handler function. By either binding the function to an object or using an arrow function, the context can be explicitly stated and controlled, leading to cleaner code.

Context sensitivity in JavaScript is an indicator of the language's flexibility in handling and manipulating contexts. With runtime context assignments, JavaScript enjoys the advantage of dynamically changing the value of 'this' as per the invoking context.

Turn to the example of an eCommerce site with a shopping cart. Each item in the cart is an object, and on clicking the 'remove' button, the item is removed. Understanding context here enables us to use the 'this' keyword effectively to specifically refer to the item that needs to be removed, without having to loop through the entire cart to identify the item.

In essence, command over the context in JavaScript and TypeScript significantly aids in debugging, writing clean code, and grasping the languages' complexities. With these insights on context and 'this,' manipulating data within functions and navigating through scopes become seamless.

Binding and the 'bind' keyword in JavaScript/TypeScript

The 'bind()' keyword in JavaScript and TypeScript is used to explicitly set the value of 'this' for a function. This technique is often used to call a function with a specific execution context, which is crucial in scenarios where the context is not inherently defined, such as in callbacks or event handlers.

Rationale for using 'bind()' in JavaScript and TypeScript

Often, in JavaScript and TypeScript, the value of 'this' inside a function can change depending on how the function is invoked. This can lead to some unexpected behaviors, especially in contexts where 'this' needs to be predictable. To prevent such unpredictability, the 'bind()' method can be used. By using 'bind()', we can explicitly set a specific value for 'this' regardless of how the function is invoked, providing a reliable and predictable context.

Let's take a look at a sample use case to understand 'bind()' in action. Suppose we have a button and we wish to click on it to change its text. This can be achieved in both JavaScript and TypeScript.

JavaScript:

var buttonElement = document.querySelector('button');
buttonElement.textContent = "click me";

function changeButtonTextJS() {
    // 'this' refers to the button being clicked
    this.textContent = "clicked";
}

// using 'bind()' to attach the 'changeButtonTextJS' function to the button click
buttonElement.addEventListener('click', changeButtonTextJS.bind(buttonElement));

TypeScript:

Here is how you'd achieve the same in TypeScript.

let buttonElement: HTMLButtonElement = document.querySelector('button') as HTMLButtonElement;
buttonElement.textContent = "click me";

function changeButtonTextTS(this: HTMLButtonElement) {
    this.textContent = "clicked";
}

buttonElement.addEventListener('click', changeButtonTextTS.bind(buttonElement));

As you can see, in TypeScript, we obtain strong type safety through explicit type declaration.

Now let's compare the 'bind()' function in JavaScript with other similar functions:

  • The 'bind()' method creates a new function that, when called, has its 'this' keyword set to the provided value.
  • The 'call()' method is similar to 'bind()' wherein it changes the context of 'this'. However, unlike 'bind()', 'call()' executes the function immediately and allows you to pass in arguments one by one.

The 'on' and 'click' functions in JavaScript and TypeScript also leverage similar principles in managing context and triggering events. However, they come with their own set of advantages and scenarios they're best suited for.

In conclusion, the 'bind()' function in JavaScript and TypeScript is a powerful tool used to manipulate 'this' in your functions. It allows us to explicitly define the execution context and to craft our functions to behave exactly as we need them. Understanding and effectively utilizing the 'bind()' method will lead to more robust and predictable code. Given the subtleties of 'this' in JavaScript, it is a handy method to have in your repertoire.

Now, as a challenge, I encourage you to explore the uses of 'apply()' in JavaScript, similar to 'call()', and how it can be leveraged alongside 'bind()'. Understanding the difference between 'apply()' and 'call()' can lead to code that is clearer and easier to debug.

Implicit Binding in JavaScript

JavaScript has a feature known as 'implicit binding', which provides utility once understood. Implicit binding happens when you invoke a function with a preceding dot, where the object before that dot transforms into this.

Consider this example:

const person = {
  name: 'John',
  greet: function() {
    console.log('Hello, my name is ' + this.name);
  }
};
person.greet();  // Hello, my name is John

In this example, this in the function greet is an implicit reference to the person object, because greet is invoked with person preceeding it.

Let's examine another example involving a nested object.

const sports = {
  type: 'Football',
  teams: [
    { 
      name: 'Team1',
      members: [
        {
          name: 'Player1',
          greet: function() {
            console.log('Hello, I am ' + this.name + ' from ' + this.team.name);
          }
        }]

It's crucial to note that this only binds to the object that makes the function call or the immediate parent object. Therefore, when you want to use implicit binding correctly, always consider the object that calls the function: 'Where was the function invoked and what is to the left of the dot?' - these two questions will help you understand which object this will refer to.

The primary advantage of implicit binding is that, it allows creating methods for multiple object reusability. However, with nested objects or callbacks, this might not be referring to what you anticipate. Make sure to remain cautious and mindful using implicit binding in such scenarios.

To wrap it up, implicit binding is beneficial in JavaScript. It enables a dynamic relationship between the object and the function, promoting code reusability. Utilize this feature wisely to maintain a DRY and efficient codebase. Be aware of its behavior with nested objects and functions within functions.

'this' keyword in JavaScript/TypeScript frameworks

In JavaScript, the this keyword tends to be one of the most befuddling aspects of the language for newcomers and even some seasoned developers. this is a keyword that signifies the context in which a function is invoked. In the realm of object-oriented programming, this is frequently employed to represent the object within which a method was invoked. Similarly, this holds immense significance in TypeScript and various JavaScript frameworks, including React. Let's delve into the intriguing behaviors of this.

JavaScript and this

In traditional JavaScript, the value of this inside a method is the object to which the method belongs. Let's illustrate this concept with a simple example:

const obj = {
    name: 'JavaScript object',
    printName: function() {
        console.log(this.name);
    }
};

obj.printName(); // logs: 'JavaScript object'

In this example, the printName function prints the name attribute of the obj object, hence 'JavaScript object' is outputted.

However, the value of this when utilized outside of an object, such as within a function in the global scope, differs:

function printThis() {
    console.log(this);
}

printThis(); // logs: window (in a browser outside strict mode) or global in Node.js

Here, the value of this is window in a browser outside strict mode, or global in Node.js. This peculiarity implies how crucial it is to consciously handle the usage of this inside nested functions and other contexts.

this in React components

In React, the concept of this carries a distinctive connotation. Inside class component methods, the value of this corresponds to an instance of the class.

Let's ponder this through an example:

class ExampleComponent extends React.Component {
    constructor() {
        super();
        this.handleEvent = this.handleEvent.bind(this);
    }

    handleEvent() {
        console.log(this);
    }

    render() {
        return (
            <button onClick={this.handleEvent}>Click me</button>
        );
    }
}

In this instance, the handleEvent method uses the .bind method in the constructor to ensure the this binding to the ExampleComponent instance. The this binding primarily refers to the instance of ExampleComponent. If the method were defined via arrow function syntax, this would maintain the same value due to its lexical scope preservation attribute.

this in TypeScript

The this keyword in TypeScript, much akin to JavaScript, introduces numerous specifications owing to its robust support for static types. One of these features is 'this typing', facilitating us to specify the type that this should be in a context. Let's illustrate this:

class MyClass {
    myMethodName(this: MyClass) {
         return this;
    }
}

In the above example, this inside myMethodName has been explicitly defined to refer to the instance of MyClass.

In the TypeScript universe with React, arrow functions in class components can be harnessed to retain the correct this binding while adhering to type safety.

Let's view this in practical terms using a TypeScript class component:

class MyClass extends React.Component {
    myMethodName = () => {
        console.log(this);
    }

    render() {
        return <button onClick={this.myMethodName}>Click me</button>;
    }
}

In this instance, the arrow function helps to maintain the this context to refer to the instance of MyClass.

Comparison of this between JavaScript and TypeScript

While JavaScript is untyped and dynamic, TypeScript is a statically typed superset of JavaScript. This feature of TypeScript infuses rigorous checks at compile-time.

One of the aspects governed by such checks is the usage of this. TypeScript's concept of 'this typing' requires you to explicitly define what this should point to in your methods. This enables more precision and facilitates a friendlier JS to TS transition.

In a practical scenario, the typos in the this keyword in TypeScript with React could be caught compile-time. It prevents misbehavior at runtime. Consider an instance where a developer adopts the JavaScript methodology to define event handlers in a React class component.

An error would be caught during compilation if the this context, intended to reference the class instance, is typed incorrectly; thus, safeguarding against potential run-time failures.

Let's illustrate this with an example:

class MyClassComponent extends React.Component {
    private data: string;

    private renderData(this: MyClassComponent) {
        console.log(this.data);
    }

    render() {
        return (
            <div onClick={this.renderData}>
                Click me
            </div>
        );
    }
}

In this TypeScript class component, a compilation error will be thrown, because this inside function renderData is not bound and ends up referring to undefined at run-time.

Challenge: Try to refactor the above MyClassComponent in TypeScript such that this inside function renderData refers to the correct context. As a continuation, consider method chaining in JavaScript and TypeScript using the this keyword, and try composing methods that return this to observe how method chaining can make your code more elegant.

Practical Applications of 'this', context, and binding in JavaScript

Understanding 'this' in Traditional Functions

First, let's investigate the significance of this in regular functions in JavaScript. this is a context-dependent value ascertained when a function is invoked. Routinely, in the global scope, this points towards the global object (window in the browser setting). However, within a function's scope, this refers to the object that has called upon the function. Here is a straight forward example:

// Global scope
console.log(this); // 'this' will output Window object 

function myFunction(){
    console.log(this);
} 

const myObject = {
    a: 10,
    b: myFunction
};

myObject.b(); // Here, 'this' will point to myObject 

In the preceding example, a function myFunction is described that does nothing more than logging the context (this). Notice, when the function is invoked from myObject, this within myFunction equates to myObject, the initiator object.

Understanding 'bind'

Moving onto our next concept, bind method lets us deliberately bind a context (this) to any function. This method returns a fresh function with its context set as the argument passed, regardless of its invocation method or location.

function myFunction(){
    console.log(this.x);
} 

const myObject = {
    x: 10
};

const boundFunction = myFunction.bind(myObject);

boundFunction();     // Outputs: 10 

Important: The bind method not only binds this to a context but also enables partial functions. Any arguments passed into bind after the context will become the defaulted leading arguments to the bound function.

Understanding 'this' in Arrow Functions

The distinct separation between regular functions and arrow functions stems from the way they deal with this keyword. Contrary to traditional functions, arrow functions do not create their own context. Instead, they inherit this from the scope of their definition.

const myObject = {
    x: 10,
    regularFunc: function() { console.log(this.x); },
    arrowFunc: () => console.log(this.x)
};

myObject.regularFunc();    // Outputs: 10 
myObject.arrowFunc();      // Outputs: undefined 

In this case, regularFunc is a conventional function and rightly logs the context's x property. However, arrowFunc does not possess its context (this), thereby logging undefined.

In summary, understanding how your function operates and the behaviour of this in different contexts is essential for writing effective and bug-free JavaScript or TypeScript code. It highlights the subtle differences between traditional and arrow functions. It also emphasizes bind's role in managing function contexts.

As we reach the end of our learning journey for this article, let's look at a little challenge. Can you write both traditional and arrow function that logs its own context when invoked and bind it to a different object with a value 10? Share your code in the comments!

Exploring methods of 'this' keyword in JavaScript

The core principle in JavaScript that guides context determination is the way the function is being invoked (i.e., where the function was called). In JavaScript, this keyword is used to refer to the object on which the function (in which this is used) is invoked. Several methods can be attached to this - their role is to provide different context values for this.

Default Binding

Default binding applies when you invoke a function standalone, without any context. Undoubtedly, this is a very common scenario. JavaScript's rule dictates that in such cases where we don't provide any context, the global object, window (in the browser), is used as the value for this.

function whoAmI() {
  console.log(this);
}

whoAmI();   // Logs the global object (window in the browser)

"This" in the above example represents the global object because the whoAmI function was invoked without any context.

In strict mode, however, default binding behaves differently - this does not point to the global object, but is undefined.

"use strict";
function whoAmI() {
  console.log(this);
}
whoAmI();  // undefined

Implicit Binding

Functions that are invoked as methods, in the form of object.method(), use implicit binding. Here, this refers to the object that calls the method.

let person = {
  name: 'John',
  sayHello: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

person.sayHello(); // Hello, my name is John

In the above example, the object person invokes the method sayHello.

Explicit Binding

Explicit binding is when we use the call(), apply(), or bind() methods to set the this value. These methods allow us to manually set what we want to reference with this.

let person = {
  name: 'John'
}

function sayHello() {
  console.log(`Hello, my name is ${this.name}`);
}

sayHello.call(person);  // Hello, my name is John

In this example, we're using the call method. We're passing in the person object which sets this to person.

Constructor Function

When a function is invoked with new, this is bound to the new object that's being constructed.

function Person(name) {
  this.name = name;
  this.sayHello = function() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

let john = new Person('John');
john.sayHello();  // Hello, my name is John

In the example above, this refers to the new object being created by the Person constructor function.

Don't Get Left Behind:
The Top 5 Career-Ending Mistakes Software Developers Make
FREE Cheat Sheet for Software Developers