Careful with this

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the javascript category.

Last Updated: 2024-04-24

The following code failed, saying that this.createUserAccount is not a function (even though the function was defined on the same object). The issue was that this became another object during the time the callback was called:

window.SemicolonAndSons = {
  createUserAccount(email),
  videoReady(video) {
    video.bindEvent("conversion", function(email) {
      this.createUserAccount(email) // fails
    })
  },
}

Two fixes are available:

First, rewrite with arrow function (since arrow functions cause this to be preserved as the object it referred to outside the arrow function (here thanks to the way videoReady uses the method syntax it will be the SemicolonAndSons object)

windown.SemicolonAndSons = {
  // Note that if object syntax were used (videoReady: {} then `this` would be
  // undefined)
  videoReady(video) {
    video.bindEvent("conversion", (email) => {
      this.createUserAccount(email)
    })
  },
}

Fix two: keep the normal function, but bind that function to this

video.bindEvent("conversion", function(email) {
  this.createUserAccount(email)
}.bind(this))

--

Here's a general example that should make things clearer

function multiply(p, q, callback) {
    callback(p * q);
}

let user = {
    x: 2,
    y :3,
    userMultiply: function() {
    // Inside a method call, x and y will always refer to 
    // the user's data, assuming the method gets called on the `user` object
        multiply(this.x, this.y, function(sum) {
      // HOWEVER, inside this callback, which gets executed outside of
      // the user object in the top-level `multiply` function, `this` will not
      // be the `user`
            console.log(sum); // Prints 6, as expected, since it is working on the args passed to multiply: p and q
            console.log(this === window); // Prints true // i.e. this is not the user, but the window
        })
    }
}

// Now for something really weird:
let outerMultiply = user.userMultiply;
outerMultiply()
// Sum is NAN - i.e. x and y are not defined since this function is no longer being // called on the user!

// vs.
user.userMultiply()
// Sum is 6 - i.e. x and y ARE defined when called like this.

Resources