JavaScript - Owner of “this”

I followed a tutorial for creating a JavaScript stopwatch and am trying to expand it to work with multiple stopwatches (multiple instances of a class). The problem I have is when I am trying to display the current value while the clock is ticking I need to hard code the class instance becasue using "this" doesn't work (on the line where I am using console.log). I have cut the code down to the minimum to try to understand this aspect, and have pasted what I have below:

function Timer(){
    var time1 = null;
    var time2 = null;
    var timeLoop = null;

    function getTime(){
        var day = new Date();
        return day.getTime();
    }

    this.start = function(){
        time1 = getTime();

        timeLoop = setInterval(function(){
            time2 = getTime();
            console.log(_Timer.duration());
            //console.log(this.duration());
        },500);
    }

    this.duration = function(){
        return (time1 - time2) / 1000;
    }

}       

I think the link below describes my problem but I don't understand it enough to apply it here. Is the issue due to the owner being this.start rather than just this and how can I ammend the code to make it work with any instance of Timer?

http://www.quirksmode.org/js/this.html

I have included both the hard coded value line and the "this" line that doesn't work.

Thanks,

Geraint

Answers:

Answer

If you want to have the this property be consistent, you should bind the functions that are being called.

For example,

setInterval(function() { /* code here */ }.bind(this), 500)

That way, the this of the inner function will be the same as that of the outer function.

Answer

Whenever you see function you can assume the value of this changes, so inside the callback function for the interval this is actually the window, not the object.

The easy solution is to just store this in a variable

function Timer(){
    var time1 = null;
    var time2 = null;
    var timeLoop = null;

    function getTime(){
        var day = new Date();
        return day.getTime();
    }

    this.start = function(){

        var self = this;

        time1 = getTime();

        timeLoop = setInterval(function(){
            time2 = getTime();
            console.log(self.duration());
        },500);
    }

    this.duration = function(){
        return (time1 - time2) / 1000;
    }

}    
Answer

this is not a local variable, so it doesn't get saved in closures. You need to assign a local variable:

this.start = function(){
    var self = this;
    time1 = getTime();

    timeLoop = setInterval(function(){
        time2 = getTime();
        console.log(self.duration());
    },500);
}
Answer

Try:

function Timer(){
    var time1 = null;
    var time2 = null;
    var timeLoop = null;
    var _this = this;

    function getTime(){
        var day = new Date();
        return day.getTime();
    }

    this.start = function(){
        time1 = getTime();

        timeLoop = setInterval(function(){
            time2 = getTime();
            console.log(_this.duration());
        },500);
    }

    this.duration = function(){
        return (time1 - time2) / 1000;
    }

}       
Answer

First things first. Javascript doesn't support class based OOP. It's OOP along with it's inheritance is prototypal.

Following is an example of how to implement prototypal OOP features with your timer example:

function Timer(){
  var time1 = null;
  var time2 = null;
  var timeLoop = null;
}

Timer.prototype.getTime = function(){
    var day = new Date();
    return day.getTime();
}

Timer.prototype.start = function(){
    time1 = this.getTime();
    timeLoop = this.setInterval(function(){
        time2 = this.getTime();
        console.log(this.duration());
    }.bind(this),500);
}

Timer.prototype.duration = function(){
    return (time1 - time2) / 1000;
}

Look at the Custom Objects section of MDN's Javscript Reintroduction

There's nothing wrong with the way it's shown in your tutorial. It's just that this is a cleaner way and the bind call is only needed for the console.log statement which would otherwise associate this as the window. If you get rid of it you can get rid of the bind too.

Answer

Using bind(this) works perfectly.

var timer = {
  start: function() {
    setInterval(function() {console.log(this.duration());}.bind(this), 1000);
  }
}

timer.start();

But it can be unnecessary if you are using const with an arrow function:

const timer = {
  start() {
    setInterval(() => {console.log(this.duration());}, 1000);
  }
}

timer.start();

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.