Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

this.method Is not function with setInterval [duplicate]

I have this simple code:

var Modules = (function() {
    'use strict';

    return {
        
        TIMER: function (){
            var timer = null;
            
            return {
                time: 100,
                init: function() {
   
                    this.counter();
                    this.timer = window.setInterval(this.counter, 1000);
                },
                counter: function() {
                    this.time -= 1;

                    if (this.time <= 0) {
                        window.clearInterval(this.timer);
                        alert('Time expired');
                    }
                    console.log(this.time);
                    this.viewer();
                    
                },
                viewer: function() {
                    document.getElementById('timer').innerHTML = this.time;
                }
            }
        }
    };
}());

Modules.TIMER().init();
<div id="timer"></div>

And something is wrong because I got 2 errors:

this.viewer is not a function

and

NaN of this.time

What it wrong with my design pattern running over interval?

After extend TIMER to reset method:

reset: function() {
                    this.time = 100;
                }

and call it outside as: Modules.TIMER().reset(); ?

I got

this.time is not defined

.

Or inside init:

jQuery("body").on('keyup mouseup', function (e) {
                        this.reset();
                    });

I got error:

this.reset() is not s function.

like image 434
step Avatar asked Mar 03 '23 20:03

step


1 Answers

Your issue is coming from this line:

this.timer = window.setInterval(this.counter, 1000);

When you are invoking the callback in the setInterval method, the this in the callback function no longer refers to your TIMER object, but the window.


Solution A: Use .bind(this) to bind lexical this to callback

You will need to bind the current context to the callback:

this.timer = window.setInterval(this.counter.bind(this), 1000);

var Modules = (function() {
    'use strict';

    return {
        
        TIMER: function (){
            var timer = null;
            
            return {
                time: 100,
                init: function() {
   
                    this.counter();
                    this.timer = window.setInterval(this.counter.bind(this), 1000);
                },
                counter: function() {
                    this.time -= 1;

                    if (this.time <= 0) {
                        window.clearInterval(this.timer);
                        alert('Time expired');
                    }
                    console.log(this.time);
                    this.viewer();
                    
                },
                viewer: function() {
                    document.getElementById('timer').innerHTML = this.time;
                }
            }
        }
    };
}());

Modules.TIMER().init();
<div id="timer"></div>

Solution B: Use ES6 arrow function in setInterval callback

Note: Personally, I prefer this solution because it uses ES6, but if you are still supporting legacy browsers and do not want to transpile your JS, this might not be the best solution.

Another alternative will be using an arrow function in the callback of setInterval, instead of assigning the this.counter function as the callback directly:

this.timer = window.setInterval(() => this.counter(), 1000);

The arrow function preserves the lexical this, so when this.counter() is invoked it will be using the same context, i.e. the internal this will be referencing your TIMER object.

var Modules = (function() {
    'use strict';

    return {
        
        TIMER: function (){
            var timer = null;
            
            return {
                time: 100,
                init: function() {
   
                    this.counter();
                    this.timer = window.setInterval(() => this.counter(), 1000);
                },
                counter: function() {
                    this.time -= 1;

                    if (this.time <= 0) {
                        window.clearInterval(this.timer);
                        alert('Time expired');
                    }
                    console.log(this.time);
                    this.viewer();
                    
                },
                viewer: function() {
                    document.getElementById('timer').innerHTML = this.time;
                }
            }
        }
    };
}());

Modules.TIMER().init();
<div id="timer"></div>
like image 52
Terry Avatar answered Mar 18 '23 23:03

Terry