4

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.

step
  • 1,625
  • 2
  • 19
  • 35
  • 1
    Possible duplicate of [How does the "this" keyword work?](https://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work) – VLAZ Jun 19 '19 at 06:48
  • OK I made getter and setter for variable, moved reset function as private and now is possible reset it. Still I cannot reset it from outside call by: Modules.TIMER().reset(); – step Jun 19 '19 at 10:56

2 Answers2

8

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>
Terry
  • 48,492
  • 9
  • 72
  • 91
-1

I found answer for reset from outside. I changed TIMER like this:

TIMER: (function (){
    // all stuff
}())

Then call is like: Modules.TIMER.reset(); and then it works as expected.

step
  • 1,625
  • 2
  • 19
  • 35