3

I think that code is worth a thousand words. Take this example

class Cat {

 constructor() {
  this.meow("roar", this.sound)
 }

 meow(a, callback) {
  callback(a)
 }

 sound(a) {
  console.log(a)
  console.log(this.sayMeow) <----- THIS IS UNDEFINED
 }

 sayMeow() {
  return "Meow"
 }
}

As you can see method sayMeow() is undefined. Can you please explain why and how can i solve it?

This is just simplified representation of more complex code where i have to use callbacks. I need to know why method is undefined inside callback function. Please do not write modifications of this simple Cat class.

Thank you

Raold
  • 1,383
  • 4
  • 20
  • 33
  • 3
    In javascript, `this` is determined by how a function is called, rather than where it is defined. Since you pass `this.sound` to another function as a callback, when it's called, it will reference a different `this` than what you expect. You may be interested in this question: [How to access the correct `this` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback) – CRice Mar 10 '19 at 14:27

2 Answers2

5

Explanation

In JavaScript, the this is determined when a function is called. In your case, sound(a) is called with the this context of undefined.

if you insist on using this inside sound(a), there are two common solutions:

  • use a class property function (an arrow function) instead of a method
  • bind the this context in the constructor

The first approach was popularized by React, and it worked fine because user-created React components are meant to be final classes. However, if you are doing object-oriented programming and you expect your Cat class to be inherited from, you cannot use this approach for it breaks inheritance.

Solution

Bind the context of execution to the instance of your class in the constructor.

class Cat {
    constructor() {
        this.sound = this.sound.bind(this)
        this.meow("roar", this.sound)
    }

    meow(a, callback) {
        callback(a)
    }

    sound(a) {
        console.log(a)
        console.log(this.sayMeow)
    }

    sayMeow() {
        return "Meow"
    }
}
Karol Majewski
  • 23,596
  • 8
  • 44
  • 53
3

This is because of Javascript this context when passing this. When passing this you should always bind it to the current context

The this keyword is bound to different values based on the context in which it is called. With arrow functions however, this is lexically bound. It means that it uses this from the code that contains the arrow function.

Arrow function:

() => {}

Arrow Functions lexically bind their context so this actually refers to the originating context.

class Cat {

 constructor() {
  this.meow("roar", this.sound)
 }

 meow(a, callback) {
  callback(a)
 }

 sound = (a) => {
  console.log(a)
  console.log(this.sayMeow)
 }

 sayMeow() {
  return "Meow"
 }
}
MCMatan
  • 8,623
  • 6
  • 46
  • 85
  • This approach is safe only when `Cat` is not inherited from. If it is, there will be [a runtime error](https://links.wtf/4rvl). – Karol Majewski Mar 10 '19 at 15:19
  • This worked as advertised. Not planning on using inheritance with my solution and could not get @Karol Majewski's binding trick to work as the method is being called back from a different class. – Michael Nov 29 '22 at 21:42