0

I can understand that A doesn't work because I'm calling from outside, but why doens't B work? I thought when a method is calling another method, this refers to the object?

const foo = {
  bar: 'bar',
  sayBar() {
    console.log(this.bar);
  },
  sayBar3times() {
    [1, 2, 3].forEach(this.sayBar);
  },
};

/* test A */ [1, 2, 3].forEach(foo.sayBar); // doesn't work
/* test B */ foo.sayBar3times(); // doesn't work either

I can understand

Quuxuu
  • 549
  • 1
  • 4
  • 10

2 Answers2

1

That is because when you do [].forEach(this.sayBar), the this actually refers to the windows object. When you invoke this.sayBar using an arrow function, then you pass on the lexical this (which is the object itself) to the function.

Note: If passing the callback function uses an arrow function expression, the thisArg parameter can be omitted, since all arrow functions lexically bind the this value.

Source

For this very reason, that is why Array.prototype.forEach accepts thisArg as the second positional argument, so you can do [].forEach(this.sayBar, this) and it will work:

const foo = {
  bar: 'bar',

  sayBar() {
    console.log(this.bar);
  },

  sayBar3times() {
    // this doesn't work:
    [1, 2, 3].forEach(this.sayBar);
    
    // this works:
    [1, 2, 3].forEach(this.sayBar, this);
    
    // this works:
    [1, 2, 3].forEach(() => {
      this.sayBar();
    });
  },
};

foo.sayBar3times();

With regards to your updated code, the answer is the same:

  • Test A does not bind the foo in the callback, so this refers to the global object window. This can be fixed by simply doing [1, 2, 3].forEach(foo.sayBar, foo). In this case, the this inside the method call will now refer to the foo object.

  • Test B does work because the sayBar3times lacks binding context in the forEach() as well. So, if you provide thisArg correctly, by doing [1, 2, 3].forEach(this.sayBar, this) it will work

In both examples, you will need to provide a thisArg to the callback in Array.prototype.forEach:

const foo = {
  bar: 'bar',
  sayBar() {
    console.log(this.bar);
  },
  sayBar3times() {
    [1, 2, 3].forEach(this.sayBar, this);
  },
};

/* test A */
[1, 2, 3].forEach(foo.sayBar); // doesn't work
[1, 2, 3].forEach(foo.sayBar, foo); // works

/* test B */
foo.sayBar3times(); // works
Terry
  • 63,248
  • 15
  • 96
  • 118
  • Thanks so much for your answer! I'll mark it as correct soon. I slightly changed the wording of my question to reflect my thinking, please feel free if you'd like to edit the answer one more time. Thanks! – Quuxuu Apr 03 '20 at 22:28
  • @Quuxuu Updated my answer with your new code. – Terry Apr 03 '20 at 22:33
0

You should research the usage of bind in JavaScript:

[1, 2, 3].forEach((this.sayBar).bind(this))
Majed Badawi
  • 27,616
  • 4
  • 25
  • 48