The setTimeout
does not keep the variable these
according to the value it contained at the time it was called. It will always pick up the current reference. At the end of the first ten seconds, the loop each
is already over and the reference in these
will be the last element. And it will be so for all other times that the setTimeout
execute.
count = 1;
$("a.btns").each(function() {
if ($(this).parent("span").attr("id") == "btn_default") {
return;
}
count++;
these = $(this);
setTimeout(function() {
$(these).click();
}, 1000 * count);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
<a class="btns" onclick="console.log(1)"></a>
</span>
<span>
<a class="btns" onclick="console.log(2)"></a>
</span>
<span>
<a class="btns" onclick="console.log(3)"></a>
</span>
This is a common problem that happens when we don’t understand what the closures in Javascript and how it works with the scopes/contexts of functions.
The solution would be to find a way to keep the reference to the button element when calling the function inside the setTimeout
. You can play your setTimeout
for a parameterized auxiliary function (maintaining these
in a different context, unrelated to the context of the function in which it was called):
count = 1;
$("a.btns").each(function() {
if ($(this).parent("span").attr("id") == "btn_default") {
return;
}
count++;
these = $(this);
clickTimeout(these, 1000 * count);
});
function clickTimeout(el, time){
setTimeout(function() {
$(el).click();
}, time);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
<a class="btns" onclick="console.log(1)"></a>
</span>
<span>
<a class="btns" onclick="console.log(2)"></a>
</span>
<span>
<a class="btns" onclick="console.log(3)"></a>
</span>
Or you can use the method bind() Javascript to change the context of this
within the function being passed to the setTimeout
. We can pass the variable these
as the new this
function and use it in there without losing your reference:
count = 1;
$("a.btns").each(function() {
if ($(this).parent("span").attr("id") == "btn_default") {
return;
}
count++;
these = $(this);
setTimeout((function(el) {
this.click();
}).bind(these), 1000 * count);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
<a class="btns" onclick="console.log(1)"></a>
</span>
<span>
<a class="btns" onclick="console.log(2)"></a>
</span>
<span>
<a class="btns" onclick="console.log(3)"></a>
</span>
I hope I’ve helped.
EDITION
In Javascript ES6 were inserted the variables of type Let and const. They add an interesting feature called Temporal Dead Zones, which allows them to retain the value they contained in the scope when they were executed. It’s a little hard to understand concept.
In practice, his each
works as expected with only a small change in the original code:
count = 1;
$("a.btns").each(function() {
if ($(this).parent("span").attr("id") == "btn_default") {
return;
}
count++;
const these = $(this); // <--- Agora utiliza "const"
setTimeout(function() {
$(these).click();
}, 1000 * count);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
<a class="btns" onclick="console.log(1)"></a>
</span>
<span>
<a class="btns" onclick="console.log(2)"></a>
</span>
<span>
<a class="btns" onclick="console.log(3)"></a>
</span>
Note that now the variable these
is declared using const
. This way, your code becomes more intuitive and easy to understand, without having to break it (and thus affect readability) to take into account the and the contexts of this
.
Finally, we have yet another way to solve your problem by using Arrow functions, which are also a new Javascript functionality ES6:
count = 1;
$("a.btns").each(function() {
if ($(this).parent("span").attr("id") == "btn_default") {
return;
}
count++;
setTimeout(() => $(this).click(), 1000 * count);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span>
<a class="btns" onclick="console.log(1)"></a>
</span>
<span>
<a class="btns" onclick="console.log(2)"></a>
</span>
<span>
<a class="btns" onclick="console.log(3)"></a>
</span>
As Arrow functions do not have their own context this
, and therefore use the this
of the lexical context that is just above. This means that when your Arrow Function use the $(this)
, she will be accessing the object this
of the iteration of each
in which it was called, also retaining the reference of the button element.
The
setTimeout
does not maintain thethese
according to the value it contained at the time it was called. It will always pick up the current reference. At the end of the first ten seconds, the loopeach
is already over and the reference inthese
will be the last element. And it will be so for all other times that thesetTimeout
execute.– Pedro Corso