1

I don't understand why using function name in setInterval is not working correctly but passing annonymous function is working just right.

not working example (it's console logging NaN and before calling first time this.counter++ it's returning undefined as it couldn't find the variable?)

export class MyClassName {
 counter = 0;

 startInterval(){
   setInterval(this.myFunc , 1000)
 }

 myFunc(){
   this.counter++;
   console.log(this.counter)
 }
}

but with startInterval changed like below it's working correctly

startInterval(){
  setInterval(() => this.myFunc() , 1000)
}

and in html we have

<button (click)="startInterval()">Start</button>
Kaspazza
  • 303
  • 1
  • 3
  • 13
  • 5
    Because the `this` has a differnt context in your calls. Try `this.myFunc.bind(this)`. – Lain May 24 '19 at 12:12
  • Your code will not run as-is. It has more than one syntax error. Please update the code with a working example. – Todd Chaffee May 24 '19 at 12:16
  • 1
    @ToddChaffee I think it's an excerpt of a class – Christian Vincenzo Traina May 24 '19 at 12:19
  • @Lain Can you elaborate more on that? – Kaspazza May 24 '19 at 12:20
  • @Kaspazza questions should provide working examples. Otherwise we are guessing. https://stackoverflow.com/help/minimal-reproducible-example – Todd Chaffee May 24 '19 at 12:23
  • @ToddChaffee it is part of code because I am not asking how to make it work (as I wrote it works already) I just wanted to ask why it's working like that. So I just showed a concept. – Kaspazza May 24 '19 at 12:25
  • 1
    @Kaspazza: Not really without context. Probably once `this` refers to your class/namespace and the other time to `window`. Just put a `console.log()` on the `this` and you might find it. – Lain May 24 '19 at 12:29
  • This is why we need working code. Can't tell what `this` refers to without the context. – Todd Chaffee May 24 '19 at 12:32
  • Okay I added more code and I hope it's clear now – Kaspazza May 24 '19 at 12:38
  • 1
    Well basically like I suspected above: the arrow function automatically binds `this` of its context. The normal call does not which leads to `this` being `window` which has no variable `counter` defined. You have to `bind` it yourself explicitly. – Lain May 24 '19 at 12:40
  • https://stackoverflow.com/questions/33308121/can-you-bind-arrow-functions – Lain May 24 '19 at 12:53

3 Answers3

2

In the first example, you pass in a function reference without running it. When it runs, it runs in the global context where this is undefined, or this refers to the Window object. You can verify that it does in fact refer to the Window object by logging the value of this.constructor.name:

class MyClassName {
 counter = 0;

 // You are passing in a function name but not running it yet.
 // It will be run in a global context by setInterval.
 startInterval(){
   setInterval(this.myFunc, 1000)
 }

 myFunc(){
   // When run in global context, this no longer refers to our instance.
   console.log(this.constructor.name);
   this.counter++;
   console.log(this.counter)
 }
}

const mc = new MyClassName();

mc.startInterval();

In the second example, arrow functions use the this of where they are declared, not where they are run. So the this of the class is captured below, even though the arrow function will run in the global context.

class MyClassName {
 counter = 0;

 // Arrow functions use the `this` of where they are declared, not where they are run.
 // So the `this` of the class is captured below, even though the arrow function
 // will run in the global context.
 startInterval(){
   setInterval(() => this.myFunc(), 1000);
 }

 myFunc(){
   console.log(this.constructor.name);
   this.counter++;
   console.log(this.counter)
 }
}

const mc = new MyClassName();

mc.startInterval();

You can find a pretty good summary of exactly how this works specifically with setInterval in the MDN docs for arrow functions.

Todd Chaffee
  • 6,754
  • 32
  • 41
1

The discrepancy is with the this keyword. In javascript anonymous/arrow functions do not create this to reference the caller of the function. while a conventional function declared with function keyword will capture the caller of the function in this.

Therefore, in your first example the startInterval() is called by the button element, which doesn't have a counter variable. And in the second example the this keyword refers to the outer scope this which is the window object. I assume MyClassName gets created under the window object, therefore the counter exists.

Further reading: https://www.codementor.io/dariogarciamoya/understanding-this-in-javascript-with-arrow-functions-gcpjwfyuc

ayang726
  • 61
  • 4
-1

That's because your this is calling to the function and therefore myFunc is not defined. You can learn about this context with a quick search. Some examples: 1, 2.


You can verify this by logging this.myFunc. The same will happen to this.counter if it ever gets called.

let counter = 0;

const startInterval = () => {
  // undefined, and hence it does not exist.
  console.log(this.myFunc);
  setInterval(this.myFunc, 1000);
};

startInterval();

const myFunc = () => {
  this.counter++;
  console.log(this.counter);
};

You can simply fix your code by reordering the functions and removing this.

let counter = 0;

const myFunc = () => {
  counter++;  // Remove this from here as well.
  console.log(counter);
};

const startInterval = () => {
  // Defined, and hence it works.
  setInterval(myFunc, 1000);
};

startInterval();
nitobuendia
  • 1,228
  • 7
  • 18
  • This breaks the whole code. A `this` property is different by a closure variable. The correct solution is like indicated by @Lain in comments – Christian Vincenzo Traina May 24 '19 at 12:22
  • I am just explaining the issue with `this`. There's several ways to attach the counter and function to the `this` context like prototypes, binding, creating a closure above all, having a class, etc... – nitobuendia May 24 '19 at 12:24