This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I want to fade in 4 div boxes, one by one.
In css they have the opacity = 0.
Here my JavaScript code:
function fadeIn() {
var box = new Array(
document.getElementById('skill1'),
document.getElementById('skill2'),
document.getElementById('skill3'),
document.getElementById('skill4')
);
var pagePosition = window.pageYOffset;
if (pagePosition >= 1000) {
for (var i = 0; i < box.length; i++) {
setTimeout(function(i) {
box[i].style.opacity = "1";
}, i * 500);
}
}
}
Well, the function has to start if you scroll the page to the position 1000px and called in the body-tag:
Without the setTimeout it works, but with this function the console says:
Uncaught TypeError: Cannot read property 'style' of undefined
I'm a beginner and want to understand JS, so please don't provide an answer using jQuery.
By the time your your timeout runs, the loop has finished processing, so i will always be the last iteration. You need a closure:
for(var i = 0; i < box.length; i++) {
(function(index) {
setTimeout(function() {
box[index].style.opacity = "1";
}, index*500);
})(i)
}
The problem is the scope. When anonymous function executes inside timeout i variable has the last value of the i in the iteration. There are two solutions:
1) Use an IIFE:
for (var i = 0; i < box.length; i++) {
(function (i) {
setTimeout(function (i) {
box[i].style.opacity = "1";
}, i * 500);
})(i);
}
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);//prints out 0 1 2 3 4
}, i * 500)
})(i);
}
2) Using let:
for (let i = 0; i < box.length; i++) {
setTimeout(function (i) {
box[i].style.opacity = "1";
}, i * 500);
}
"use strict";
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, i * 500)
}
The let statement declares a block scope local variable, optionally
initializing it to a value.
Keep in mind let is feature fo ecmaScript 6.
Related
Trying to set the innerHTML of a HTML class which are four boxes, each to be set 3 seconds one after another. I can set the innerHTML without setTimeout when the innerHTML is set to a loading icon. When innerHTML is put inside setTimeout the following is returned: 'Uncaught TypeError: Cannot set property 'innerHTML' of undefined'.
Tried to debug my code sending messages to the console and searching stackoverflow but no luck.
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < 4; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
Would like to know why my innerHTML cannot be set within setTimeout here and possible solutions to my problem.
I believe this is a question of the current scope. The setTimeout function creates its own scope that has no reference to the old variable. You'll likely need to redefine what x is inside the timeout or pass the array explicitly to the timeout.
See here for how-to: How can I pass a parameter to a setTimeout() callback?
I would also recommend reading up on closers as well: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
When you use var inside the for-loop the setTimeout is actually triggered for the last value of i as in var the binding happens only once.
This is because the setTimeout is triggered when the entire loop is completed, then your i will be 4. Keep in mind that there is a closure because of the callback function you pass in the setTimeout call. That closure will now refer to the final value of i which is 4.
So in this case when the complete loop has executed the value of i is 4 but there are indexes upto 3 in x. That is why when you try to access x[4] you get undefined and you see TypeError to fix this just use let for fresh re-binding with the new value of i in every iteration:
for (let i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
Also if you cannot use let due to browser incompatibility you can do the trick with a IIFE:
for (var i = 0; i < 4; i++) {
(function(i){
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
})(i);
}
This works because var has function scope, so here in every iteration a new scope would be created along with the function with a new binding to the new value of i.
Because of missing i parameter - timeout probably used last (4) which was out of array.
But you can set&use function parameters by adding next timeout parameters.
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < 4; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function (i) {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i, i);
}
function generateRandomNumbers() {
var retVal = [];
for (var i = 0; i < 4; i++) {
retVal.push(Math.random());
}
return retVal;
}
<div class="numberBox"></div>
<div class="numberBox"></div>
<div class="numberBox"></div>
<div class="numberBox"></div>
// function to generate array of 4 random numbers
var x = document.getElementsByClassName("numberBox");
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i, x);
}
This problem is related to a very basic and popular concept of Javascript called closure. It can be solved in at least two ways:
Using let
var x = document.getElementsByClassName("numberBox");
for (let j = 0; j < 4; j++) {
x[j].innerHTML= '';
x[j].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (let i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
Using IIFE
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < 4; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout((function (j) {
x[j].innerHTML= '';
x[j].innerHTML = randomNums[j];
})(i), 3000 * i);
}
You are trying to access x in a inner method, that is x is not defined in the scope of setTimeout that is why you receive that execption
I would suggest you use a setInterval function as the solution
Your code:
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
A work around
var randomNums = generateRandomNumbers();
let i = 0;
let interval = setInterval(function() {
if( i != 2){
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
i += 1;
} else {
clearInterval(interval) ;
}
}, 3000);
This issue appears to be caused by several factors.
Defining the generateRandomNumbers() function outside the setTimeout() scope.
Using the var definition inside your for loop.
function generateRandomNumbers() {
return Math.floor(Math.random() * 999999) + 10000;
}
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < x.length; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
for (let i = 0; i < x.length; i++) {
setTimeout(function() {
x[i].innerHTML= '';
x[i].innerHTML = generateRandomNumbers();
}, 3000 * i);
}
This is a different implementation, but here's a Fiddle
I'm not exactly sure what the problem here is... oneTime is an array that only contains "init"; doLoop is an array that only contains "update"; When I try to call the function from oneTime it generates the error Uncaught TypeError: Cannot read property 'init' of undefined. If, in that same spot, I log this to the console, the prototype shows the init function as expected.
The really weird part, to me anyway, is that the functions called in exactly the same way from doLoop execute just fine.
Any ideas?
State.prototype.run = function() {
let _that = this;
// Case 1
for (var a = 0; a < oneTime.length; a++) {
this.prototype[oneTime[a]]();
}
// Case 2
for (var a = 0; a < oneTime.length; a++) {
_that.prototype[oneTime[a]]();
}
// Case 3
!function() {
for (var a = 0; a < oneTime.length; a++) {
_that.prototype[oneTime[a]]();
}
}();
// Case 4
this.run.intervala = setInterval(function() {
for (var a = 0; a < oneTime.length; a++) {
_that.prototype[oneTime[a]]();
}
}, 1000 / fps);
// Case 5
this.run.interval = setInterval(function() {
for (var a = 0; a < doLoop.length; a++) {
_that.prototype[doLoop[a]]();
}
}, 1000 / fps);
}
-- EDIT --
Did some more playing around... Cases 1, 2, and 3 all return Cannot read property 'init' of undefined However, Case 4 executes just fine... albeit in a loop. What am I missing here?
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
Why is this function only sending the number 10 ten times. I want it to send 1... 2 ... 3 ... 4 ... 5 and so on
but instead its showing
10....10.... 10... 10... I'm not sure why it would.
How do I make a loop that returns distinct values?
for (i = 0; i < locations.length; i++) {
setTimeout(function() { alert("test"+i.toString()) ; }, 100);
}
How do I make a loop that returns distinct values?
You can do this by using a closure (pass i back into an immediately invoked function expression (IIFE)). This will maintain the value of i:
for (var i = 0; i < 10; i++) {
(function(i) {
setTimeout(function() {
console.log("test" + i);
}, 100);
})(i);
}
To increment the timeout by using the i works the same way. Making sure to wrap the entire timeout call with the IIFE:
for (var i = 0; i < 10; i++) {
(function(i) {
setTimeout(function() {
console.log("test" + i);
}, i * 100);
})(i);
}
I'm trying to make a few functions to work one after the other with a waiting time of 1.5 seconds between them.
NOW, when i try doing so with the same Id (Inside the "NoteList(>here<)", like 1, 2, 3, or any other, it works;
for (var i = 0; i < 36; i++)
{
setTimeout(function () { OnClcRandom(NoteList[0]) }, i * 1000 + 1000);
}
BUT! when i try doing so with the var i, it doesn't work and gets the all of the functions in the page stuck. any idea why?
for (var i = 0; i < 36; i++)
{
setTimeout(function () { OnClcRandom(NoteList[i]) }, i * 1000 + 1000);
}
That would be because all of the functions refer to the same live i variable, not the value of the variable at the time you called setTimeout(). Which means by the time the timeouts actually run your function i will be 36.
Try this instead:
for (var i = 0; i < 36; i++) {
(function(x){
setTimeout(function () { OnClcRandom(NoteList[x]) }, i * 1000 + 1000);
)(i);
}
This executes an anonymous function on each iteration of the loop, with each execution getting its own x parameter for use in your original function.
Javascript doesn't create local scope for block. :)
And in your second example var i equal 36 (last value).
You need create local scope inside loop.
for (var i = 0; i < 36; i++) {
(function (i) {
setTimeout(.......);
}(i))
}
You also may fixed 'i' value assign it to function property:
for (var i = 0, f; i < 36; i++){
f = function _callback() { var i = _callback.i; .....};
f.i = i;
setTimeout(f, i * 1000);
}
Consider such loop:
for(var it = 0; it < 2; it++)
{
setTimeout(function() {
alert(it);
}, 1);
}
The output is:
=> 2
=> 2
I would like it to be: 0, 1. I see two ways to fix it:
Solution # 1.
This one based on the fact that we can pass data to setTimeout.
for(var it = 0; it < 2; it++)
{
setTimeout(function(data) {
alert(data);
}, 1, it);
}
Solution # 2.
function foo(data)
{
setTimeout(function() {
alert(data);
}, 1);
}
for(var it = 0; it < 2; it++)
{
foo(it);
}
Are there any other alternatives?
Not really anything more than the two ways that you have proposed, but here's another
for(var it = 0; it < 2; it++)
{
(function() {
var m = it;
setTimeout(function() {
alert(m);
}, 1);
})();
}
Essentially, you need to capture the variable value in a closure. This method uses an immediately invoked anonymous function to capture the outer variable value it in a local variable m.
Here's a Working Demo to play with. add /edit to the URL to see the code
With the let keyword you can get around this completely:
for(let it = 0; it < 2; it++)
{
setTimeout(function() {
alert(it);
}, 1);
}
Similar to above solution but self invoking inside of setTimeout function
for(var it = 0; it < 2; it++)
{
setTimeout(function(cur) {
return function(){
alert(cur);
};
}(it), 1);
}
Similar to the other solutions, but in my opinion cleaner:
for (var it = 0; it < 2; it++) {
// Capture the value of "it" for closure use
(function(it) {
setTimeout(function() {
alert(it);
}, 1);
// End variable captured code
})(it)
}
This keeps the same variable name for the capture, and does it for the entire loop, separating that from the logic of the timeout setup. If you want to add more logic inside the block, you can trivially do that.
The only thing I don't like about the solution is the repeat of "it" at the end.