Reputation: 7165
I wanted to change the background-color on a div dinamically using jQuery's css()
and it worked, but then I tried to add some delay to it, and for some reason it stopped working. What am I missing? Here's an MVC of it:
HTML:
<div id="nodelay"></div>
<div id="delay"></div>
JS:
$("#nodelay").hover(function() {
$(this).css("background-color", 'gray');
});
$("#delay").hover(function() {
setTimeout(function() {
$(this).css("background-color", 'gray');
}, 500);
});
https://jsfiddle.net/8eabfa2t/1/
Upvotes: 7
Views: 189
Reputation: 4435
This is due to the scope of this
in the function after the delay. There are a lot of ways to get passed this, and everyone has their preference. My preferred method is using the Function.prototype.bind()
method. It binds the function's this
reference to whatever you pass it as the first object.
The reason this works is hard for me to explain (it makes a lot of sense in my head), but the easiest way I can say it is that the this
being passed to .bind()
is the same this
you would call from the .hover()
call.
$("#nodelay").hover(function() {
$(this).css("background-color", 'gray');
});
$("#delay").hover(function() {
setTimeout(function() {
$(this).css("background-color", 'gray');
}.bind(this), 500);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="nodelay">t</div>
<div id="delay">t</div>
Another way that people do it is by declaring a variable outside of the function where the this
call is being messed up. Because of how scope in Javascript works, any functions declared after that variable can access it.
$("#nodelay").hover(function() {
$(this).css("background-color", 'gray');
});
$("#delay").hover(function() {
var self = this;
setTimeout(function() {
$(self).css("background-color", 'gray');
}, 500);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="nodelay">t</div>
<div id="delay">t</div>
Upvotes: 3
Reputation: 57982
From the MDN documentation:
The "
this
" problemCode executed by
setTimeout()
is called from a separate execution context to the function from whichsetTimeout
was called. The usual rules for setting thethis
keyword for the called function apply, and if you have not set this in the call or withbind
, it will default to the global (orwindow
) object in non–strict mode, or be undefined in strict mode. It will not be the same as the this value for the function that calledsetTimeout
. (Emphasis mine)
Since functions passed to setTimeout
are executed in a different context, this
is not bound. That would mean this
actually refers to window
(or undefined
in strict mode). You are essentially doing $(window).css(...)
which is not intended.
To combat this, you may use Function.prototype.bind
to bind this
context as mentioned above. From the documentation:
The
bind()
method creates a new function that, when called, has itsthis
keyword set to the provided value
Since this
outside of the setTimeout
function is the element (as jQuery does this for you via explicit this
binding like we're doing here), using $(this)
will refer to the #delay
element:
$("#nodelay").hover(function() {
$(this).css("background-color", 'gray');
});
$("#delay").hover(function() {
setTimeout(function() {
$("#delay").css("background-color", 'gray');
}.bind(this), 500);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="nodelay">Test</div>
<div id="delay">Test</div>
You can alternatively, as mentioned, capture this
before you enter the anonymous function, or explicitly mention the element in the selector. If you're using ES6, another way to do this is to use arrow functions, which do not bind their own this
, referring to that of the enclosing context.
Upvotes: 7
Reputation: 971
Great answers from others. Since everyone else pointed out problem with use of 'this' context, I won't bother to explain it more. However if I may, I would like to introduce you to an alternative and very useful jQuery $.proxy() function. It allows you to set the context of your choice. First argument to proxy function is a function to be executed. Second argument is the context, then what ever the argument you need to parse to the function. Try the example below
$("#nodelay").hover(function() {
$(this).css("background-color", 'gray');
});
$("#delay").hover(function() {
setTimeout($.proxy(function() {
$(this).css("background-color", 'gray');
}, this) , 500);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="nodelay">t</div>
<div id="delay">t</div>
Upvotes: 0
Reputation: 3020
That's not due to timeout, but related to the scope of this
.
https://jsfiddle.net/8eabfa2t/4/
Upvotes: 0
Reputation: 1927
helpfule reading: https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#The_this_problem (if that link doesn't take you right to the 'the "this" problem, search for it on the page)
Does this work for you:
$("#delay").hover(function() {
var target = $(this);
setTimeout(function() {
target.css("background-color", 'gray');
}, 500);
});
Upvotes: 0