0

I have these two classes

class Foo {
    name = 'Foo';

    foo() {
        const bar = new Bar(this.onSomething);
        bar.bar();
    }

    onSomething() {
        console.log(this);        //<= this where thing goes weird
    }
}

class Bar {
    name = 'Bar';
    constructor(onSomethingCallback) {
        this.onSomethingCallback = onSomethingCallback;
    }

    bar() {
        this.onSomethingCallback();
    }
}

const a = new Foo();
a.foo();

when the method onSomething called , this refer to the instance of Bar class instead of Foo class.

I expect this to refer to instance of Foo since the method onSomething is in Foo

Ali Faris
  • 17,754
  • 10
  • 45
  • 70

3 Answers3

1

This is expected. If you bind this.onSomething, then its this will be set, otherwise, its this will be determined based on where its called from.

This is also the reason why const $ = document.getElementById; isn't usable, but const $ = document.getElementById.bind(document); is (because getElementById probably references this within).

class Foo {
    name = 'Foo';

    foo() {
        const bar = new Bar(this.onSomething.bind(this));
        bar.bar();
    }

    onSomething() {
        console.log(this);        //<= this where thing goes weird
    }
}

class Bar {
    name = 'Bar';
    constructor(onSomethingCallback) {
        this.onSomethingCallback = onSomethingCallback;
    }

    bar() {
        this.onSomethingCallback();
    }
}

const a = new Foo();
a.foo();

See mdn for more info on this and bind for more details.

junvar
  • 11,151
  • 2
  • 30
  • 46
1

Nope, that's how JS functions work. Calling a function as foo.bar() you're passing to it foo as the this context (0-th argument, if you will). A function called as this/* Bar */.onSomethingCallback() doesn't remember that it used to be a property of a different object.

And there are arrow functions, which just take the lexical this.

class Foo {
    name = 'Foo';

    foo = () => {
        const bar = new Bar(this.onSomething);
        bar.bar();
    }

    onSomething = () => {
        console.log(this);        //<= this where thing goes weird
    }
}

class Bar {
    name = 'Bar';
    constructor(onSomethingCallback) {
        this.onSomethingCallback = onSomethingCallback;
    }

    bar = () => {
        this.onSomethingCallback();
    }
}

const a = new Foo();
a.foo();
mbojko
  • 13,503
  • 1
  • 16
  • 26
  • There's no reason to make `foo` and `bar` class fields with arrow functions, you should keep them as methods. Only `onSomething` needs to become one. – Bergi Apr 18 '19 at 19:55
0

You are loosing context of Foo instanse because in this line this.onSomethingCallback = onSomethingCallback; you just put that function "out of context" and call it from Bar instance.

There are several preferred solutions:

  1. bind onSomething in Foo constructor
class Foo {
  constructor () {
    this.onSomething = this.onSomething.bind(this);
  }
  ...
}
  1. write onSomething function with arrow style
class Foo {
  onSomething = () => {...}
  ...
}
  1. bind onSomething function just before passing it to Bar instance
class Foo {
  foo() {
    const bar = new Bar(this.onSomething.bind(this));
    ...
  }
  ...
}
lankovova
  • 1,396
  • 8
  • 15