setInterval does not seem to be calling the callback function [duplicate] - javascript

I have a simple javascript class.
One method of this class sets up a timer using setInterval function. The method that I want to call every time the event fires is defined inside the same class.
The question is, how can I pass this method as a parameter to the setInterval function?
One attempt was setInterval('this.showLoading(), 100). But doesn't work. This method access class properties, so I need the 'this' reference.
This is the sample code:
function LoadingPicture(Id)
{
this.imgArray = null;
this.currentImg = 0;
this.elementId = Id;
this.loadingTimer = null;
}
LoadingPicture.prototype.showLoading = function()
{
if(this.currentImg == imgArray.length)
currentImg = 0;
document.getElementById(this.elementId).src = imgArray[this.currentImg++].src;
}
LoadingPicture.prototype.StartLoading = function()
{
document.getElementById(this.elementId).style.visibility = "visible";
loadingTimer = setInterval("showLoading()", 100);
}

setInterval can take a function directly, not just a string.
https://developer.mozilla.org/en/DOM/window.setInterval
i.e.
loadingTimer = setInterval(showLoading, 100);
But, for optimal browser compatibility, you should use a closure with an explicit reference:
var t = this;
loadingTimer = setInterval(function(){t.showLoading();}, 100);

loadingTimer = setInterval("this.showLoading()", 100);
Firstly, don't use string arguments to setInterval/Timeout. It's dodgy in the same way as using eval, and may similarly fail with CSP security restrictions in the future. So instead:
loadingTimer = setInterval(this.showLoading, 100);
However, as you say, this will lose the owner reference so the called function won't see the right this. In the future (the newly-defined ECMAScript Fifth Edition), you will be able to bind the function to its owner with function.bind:
loadingTimer = setInterval(this.showLoading.bind(this), 100);
and if you implement function.bind yourself for browsers that don't yet have it (see the bottom of this answer), you can use this syntax today.
Otherwise, you will need to use an explicit closure, as in the example Computer Linguist just posted.

All the answers above are acceptable. I just wanted to add that the binding of this can also be solved by using an arrow function. For example, these are all equivalent to each other. However, the lexical scope is maintained when using arrow functions:
// Arrow function - my preferred method
loadingTimer = setInterval(() => this.showLoading, 100);
// .bind method
loadingTimer = setInterval(this.showLoading.bind(this), 100);
// Other method
var t = this;
loadingTimer = setInterval(function(){t.showLoading();}, 100);
Hope this helps :D

Related

Javascript scope basics : .bind or self =this?

For me the below code do the same thing but I'm not totally sure so I would like to be sure:
var self = this;
this.param = 5;
var listener = sub.on('change', function() {
self.param = 10;
});
Is it identical to ?:
this.param = 5;
var listener = sub.on('change', function() {
this.param = 10;
}.bind(this));
They're not identical, but for your purposes in the example you posted they're close enough in that you get the same effect. The goal is to have a reference to the value of this outside the event handler later when the handler is actually invoked. Both of those alternatives have that effect.
The .bind() function does a few other things that won't make any difference in a simple case like this.
edit — and definitely note the fix that #deceze posted in his answer.
Here's an example of when the two are different: if the self variable were to change, then your event handler would behave differently. It's probably not very likely that you'd change it, but it's a possibility (a bug could be introduced). With .bind(), you get a function that really can't be changed; nothing can override the binding that you set up that way.
It should be:
sub.on('change', function() {
this.param = 10;
}.bind(this))
^
misplaced parenthesis
You want to bind this for the callback function, not for on(). If you fix that, the results will be the same for your purposes.
Please also consider that there is notable performance results from both (actually they are 3) ways.
Check out https://jsperf.com/bind-vs-self-closure/17
Doing a closured reference seems to be way faster (actually 100x) than binding context.

Calling bind() with a context

I am working on a website where various objects bind to common events on $(window). However, I want to run these functions within the context of the objects that receive the triggers. (In other words, preserve "this" so it still refers to the object instead of the window when it calls the function.) How can I do this? For example, inside the object:
var someNum = 1;
$(window).bind("test", printNum);
function printNum() {
alert(this.someNum); // should return 1
}
Check out $.proxy, which you can use to create a function that always has a particular context:
$(window).bind("test", $.proxy(printNum, this));
Also, on is preferred over bind as of jQuery 1.7.
I have never been clear about object context in JavaScript, but I have been successful using this method. Laugh or admire it:
this.someNum = 1;
$(window).bind("test", printNum);
var parent = this;
function printNum() {
alert(parent.someNum); // should return 1
}

Recursive Function, setTimeout, and 'this' keyword in javascript

Let's say that I want to invoke a function with the onClick method. Like this:
<li class="inline" onclick="mve(this);" >TEMP</li>
AND I have a JS function that looks like this:
function mve(caller){
caller.style.position = "relative";
caller.style.left = (caller.style.left+20) +'px';
var foo = setTimeout('mve(caller)', 2000);
}
My problem is that the element (which caller refers to) is undefined after the initial onClick call. At least this is what Firebug is telling me.
I'm sure it's an easy solution, so how about just a simple explanation of why, and how?
Also if I run it like so:
function mve(caller){
caller.style.position = "relative";
caller.style.left = (caller.style.left+20) +'px';
}
I would think the element would move 20px right on every click, however that is not the case. Thoughts?
setTimeout() executes a string parameter in the global scope so your value of this is no longer present nor is your argument caller. This is one of many reasons NOT to use string parameters with setTimeout. Using an actual javascript function reference like this and it's quite an easy problem to solve getting the arguments passed accordingly:
function mve(caller){
caller.style.position = "relative";
caller.style.left = (caller.style.left+20) +'px';
setTimeout(function() {
mve(caller)
}, 2000);
}
For the second part of your question, caller.style.left is going to have units on it like 20px so when you add 20 to it, you get 20px20 and that's not a value that the browser will understand so nothing happens. You will need to parse the actual number out of it, add 20 to the number, then add the units back on like this:
function mve(caller){
caller.style.position = "relative";
caller.style.left = (parseInt(caller.style.left), 10) +20) + 'px';
setTimeout(function() {
mve(caller)
}, 2000);
}
Something that is missing from this function is a way for it to stop repeating. As you have it now, it goes on forever. I might suggest passing in either a number of iterations like this:
function mve(caller, iterationsRemaining){
caller.style.position = "relative";
caller.style.left = (parseInt(caller.style.left), 10) +20) + 'px';
if (--iterationsRemaining) > 0) {
setTimeout(function() {
mve(caller, iterationsRemaining)
}, 2000);
}
}
Also, you might be curious to know that this isn't really a recursive function. That's because the mve() function calls setTimeout() and then finishes right away. It is setTimeout() that executes the next iteration of mve() some time later and there is no accumulation on the stack frame of multiple function calls and thus no actual recursion. It does look like recursion from a glance at the code, but isn't technically.
It's likely that caller goes out of scope after the first call. You could preserve it by creating a variable with global scope that preserves the value of caller:
var globalCaller;
function onClickEvent(caller) {
globalCaller = caller;
mve();
}
function mve() {
globalCaller.style.position = "relative";
globalCaller.style.left = (caller.style.left+20) +'px';
var foo = setTimeout('mve()', 2000);
}
This is really ugly, though. You'll create much cleaner code by passing the id of the li element, and then calling getElementById() instead. Or better yet, use jQuery and use $("#id") syntax instead.

Object oriented design in javascript and 'this' pointer

I am creating a object in javascript like this
function myobject() {
this.myvar1 = 0;
this.myvar2 = 0;
}
myobject.prototype.a = function(){
this.myvar1 +=1;
$('#button').click(function () { // 'this' is undefined
alert(this.myvar1)
})
}
var mything = new myobject();
mything.a()
What is the proper way to pass the this pointer to an anonymous function?
For updated question:
The issue is that inside that event handler, this refers to the element you clicked on, rather than your myobject, so just keep a reference to it, like this:
myobject.prototype.a = function(){
this.myvar1 +=1;
var self = this;
$('#button').click(function () {
alert(self.myvar1)
});
}
You can test it out here.
For previous question:
What you have should work (exactly as you have it), you can test it here.
Adding the following code to the end of your snippet and running it correctly alerts '1':
mything.b();
alert(mything.myvar1);
What were you expecting? Perhaps you're incorrectly invoking mything.b() ?
To pass this to an inner function, "save" it to another variable.
myobject.prototype.a = function() {
var self = this;
self.myvar1 += 1;
$('#button').click(function () {
alert(self.myvar1);
});
}
What's happening here is that jQuery is hijacking the value of this in the anonymous function used as a handler for the click event. It's kind of confusing, but jQuery's behavior is to assign the DOM element that triggered the event to this. There are a couple ways to set the value of this explicitly (the "context") for the callback function:
Function.prototype.bind - Part of the ECMAScript 5 standard, just starting to be implemented in browsers. For legacy support, you can extract it from the Prototype library.
$.proxy - jQuery's crappy equivalent of Function.prototype.bind, which was introduced in jQuery 1.4.
I wrote a couple posts on my blog that explain these in more detail if you're interested:
Organizing JavaScript with Namespaces and Function Prototypes
Understanding jQuery 1.4's $.proxy() method
In both cases, since this will now be your object and not the triggering DOM element, you can get the triggering DOM element by inspecting the event object, which will be the second parameter to your callback function.
Others here have done a nice job explaining the issue and how to resolve without adding any dependencies, but personally I think this plugin is a better overall solution (if you don't mind the dependency):
http://markdalgleish.com/projects/eventralize/

Calling a class prototype method by a setInterval event

I have a simple javascript class.
One method of this class sets up a timer using setInterval function. The method that I want to call every time the event fires is defined inside the same class.
The question is, how can I pass this method as a parameter to the setInterval function?
One attempt was setInterval('this.showLoading(), 100). But doesn't work. This method access class properties, so I need the 'this' reference.
This is the sample code:
function LoadingPicture(Id)
{
this.imgArray = null;
this.currentImg = 0;
this.elementId = Id;
this.loadingTimer = null;
}
LoadingPicture.prototype.showLoading = function()
{
if(this.currentImg == imgArray.length)
currentImg = 0;
document.getElementById(this.elementId).src = imgArray[this.currentImg++].src;
}
LoadingPicture.prototype.StartLoading = function()
{
document.getElementById(this.elementId).style.visibility = "visible";
loadingTimer = setInterval("showLoading()", 100);
}
setInterval can take a function directly, not just a string.
https://developer.mozilla.org/en/DOM/window.setInterval
i.e.
loadingTimer = setInterval(showLoading, 100);
But, for optimal browser compatibility, you should use a closure with an explicit reference:
var t = this;
loadingTimer = setInterval(function(){t.showLoading();}, 100);
loadingTimer = setInterval("this.showLoading()", 100);
Firstly, don't use string arguments to setInterval/Timeout. It's dodgy in the same way as using eval, and may similarly fail with CSP security restrictions in the future. So instead:
loadingTimer = setInterval(this.showLoading, 100);
However, as you say, this will lose the owner reference so the called function won't see the right this. In the future (the newly-defined ECMAScript Fifth Edition), you will be able to bind the function to its owner with function.bind:
loadingTimer = setInterval(this.showLoading.bind(this), 100);
and if you implement function.bind yourself for browsers that don't yet have it (see the bottom of this answer), you can use this syntax today.
Otherwise, you will need to use an explicit closure, as in the example Computer Linguist just posted.
All the answers above are acceptable. I just wanted to add that the binding of this can also be solved by using an arrow function. For example, these are all equivalent to each other. However, the lexical scope is maintained when using arrow functions:
// Arrow function - my preferred method
loadingTimer = setInterval(() => this.showLoading, 100);
// .bind method
loadingTimer = setInterval(this.showLoading.bind(this), 100);
// Other method
var t = this;
loadingTimer = setInterval(function(){t.showLoading();}, 100);
Hope this helps :D

Categories

Resources