Unexpected 'this'

Unexpected 'this'

Binding Exceptions with this in JavaScript

·

4 min read

In the previous article, we delved into the four primary rules that dictate a this binding in JavaScript functions. While these rules generally provide a solid framework for understanding this behaviour, exceptions do exist. These exceptions can lead to unexpected binding behaviour, where the intended binding differs from the outcome dictated by the default binding rule.

Let’s explore these exceptions and learn how to identify and resolve them effectively.

This article is inspired by Kyle Simpson's book, "this & Object Prototypes," particularly Chapter 2: "this All Makes Sense Now." If you're interested in diving deeper into JavaScript's this behaviour, I highly recommend reading this book.

Ignored this

If you pass null or undefined as a this binding parameter to call(), apply(), or bind(), those values are ignored, and instead default binding takes precedence. this is substituted with the global object, undefined in strict mode.

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

var a = 2;

foo.call( null ); // 2

Leveraging Null for this Binding

Why would we want to intentionally pass something like null for a this binding, though?

Passing null as the this binding parameter in function calls might seem unusual, but it offers powerful functionalities like array spreading and partial application.

function foo(a,b) {
    console.log( "a:" + a + ", b:" + b );
}

// spreading out array as parameters 
foo.apply( null, [2, 3] ); // a:2, b:3

// partial application with `bind(..)`
var bar = foo.bind( null, 2 ); 
bar( 3 ); // a:2, b:3

Both methods require a this binding for the first parameter. If the functions in question don’t care about this, you need a placeholder value, and null might seem like a reasonable choice as shown in this snippet.

However, there’s a small risk in always using null for this binding. If a function (for instance, a third-party library function that you don’t control) makes a this reference and you've passed null, the default binding rule may cause it to unintentionally reference or modify the global object (like window in the browser).

This can lead to hard-to-find bugs. And don’t we just love that.

Safer this

Instead of passing null, it's safer to use a placeholder object as the this binding. This ensures that any unexpected usage of this is restricted to the placeholder object, preventing potential issues.

We can declare this placeholder object as ø, or any other character, and create it using Object.create(null). This object acts as a "DMZ (demilitarized zone)" object, isolating the function call from any unintended side effects related to the this binding. This DMZ is nothing more special than a completely empty, non-delegated object.

Object.create(null) is similar to {}, but without the delegation to Object.prototype, so it’s more empty than just {}.

function foo(a, b) {
  console.log("a:" + a + ", b:" + b);
}

// our DMZ empty object
var ø = Object.create(null);

// spreading out array as parameters
foo.apply(ø, [2, 3]); // a:2, b:3

// partial application with `bind(..)`
var bar = foo.bind(ø, 2);
bar(3); // a:2, b:3

By using this approach, we ensure that the function's behaviour remains predictable and free from unexpected this binding issues.

Indirection

Sometimes, assignments can lead to unexpected behaviour when it comes to function references. This is particularly evident when functions are indirectly referenced and then invoked.

Consider the following example:

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

var a = 2;

var o = { 
    a: 3, 
    foo: foo 
};

var p = { a: 4 };

o.foo(); // 3
(p.foo = o.foo)(); // 2

At first glance, you might expect p.foo() to output 4, as p has its own foo property with a set to 4.

Dont Tell Me Whats Going On GIF by Van der Valk

Let's break down what happens:

  1. The assignment p.foo = o.foo assigns the foo() function from object o to the foo property of object p.

  2. After the assignment, the expression (p.foo = o.foo) evaluates to the foo() function itself.

     (p.foo = o.foo) // ƒ foo()
    
  3. The resulting function is then immediately invoked with (), without any binding context, where this inside foo() refers to the global object (or undefined in strict mode).

Conclusion

The behaviour of this can sometimes create unexpected results, particularly when default binding takes precedence.

Explicitly setting this to null can produce unintended consequences, since it defaults to the global object or undefined in strict mode. However, by using a DMZ (demilitarized zone) object as a placeholder, we can ensure safer this binding.

Also, remember that when functions are indirectly referenced and then invoked, we may encounter unintentional behaviour where this is bound by default binding.


Curious about JavaScript's this behaviour? Check out my previous articles where we explore this and how it behaves in different contexts.

this All Makes Sense Now

this or That? What this isn’t