undefined in Closures In For Loops - javascript

var createShoutOuts = function(numbers_list){
var shoutOuts = [];
for (var j = 0; j < numbers_list.length; j++) {
shoutOuts.push(function() {
var shout_out = 'This is shout out number ' + (j+1);
console.log(shout_out + '. The number is ' + numbers_list[j]);
});
}
return shoutOuts;
};
var performShoutOuts = function(user_numbers){
var readyForShout = createShoutOuts(user_numbers);
for (var i = 0; i < readyForShout.length; i++) {
readyForShout[i]();
};
};
performShoutOuts([2,4,8]);
I created the above to teach myself closures. The output is always:
'This is shout out number 4. The number is undefined'
I understand that it will always say number 4 because the anonymous functions that are being pushed in to the shoutOuts array have a reference to the j variable, not a copy of the value of the j variable. Therefore by the time the anonymous functions are called by readyForShout[i](); the for loop has already run and i has the value 4.
What I don't understand is why is it saying undefined? Because it appears to me as though the array that is passed in to performShoutOuts should be stored in the closure and should therefore be accessible when readyForShout[i](); is executed.
What have I missed?

Try using a closure for real:
var createShoutOuts = function(numbers_list){
var shoutOuts = [];
for (var j = 0; j < numbers_list.length; j++) {
shoutOuts.push((function (j) {
return function () {
var shout_out = 'This is shout out number ' + (j+1);
console.log(shout_out + '. The number is ' + numbers_list[j]);
};
})(j));
}
return shoutOuts;
};
var performShoutOuts = function(user_numbers){
var readyForShout = createShoutOuts(user_numbers);
for (var i = 0; i < readyForShout.length; i++) {
readyForShout[i]();
};
};
performShoutOuts([2,4,8]);

Related

JavaScript how to creating dynamic variable

I am trying to create dynamic variable, for example rather saying
let f0, f1 = '';
and then using these variable in forEach
{Object.keys(shop).forEach((element, key) => {
if (element == dName[0]) {
f0 = Object.values(shop)[key];
}
if (element == dName[1]) {
f1 = Object.values(shop)[key];
}
})}
trying below,
let k = 'f';
let i = 0;
for(i = 1; i < 2; i++) {
eval('let ' + k + i + '= \'\' ;');
}
console.log("f1=" + f1);
but console printing
f1=undefined
what wrong I am doing, thanks in advance
i don't know if it is possible with eval but you could use an object to store your variables like this
let k = 'f'
let vars = {}
for(i = 1; i < 5; i++) {
vars[k+i] = ''
}
console.log(vars)
console.log("f1=" + vars.f1);

Cannot set innerHTML inside setTimeout

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

variable i is not passed into a function in a loop in JavaScript

I understand there is no block level scope for var i ; however, in the following code, d.push(); is executed after the initialisation of i, why i here remains undefined?
var d = [];
for (var i = 0; i < 3; i++) {
d.push(function(i) {
console.log('iterator: ' + i);
});
}
d[0]();//undefined
Any thoughts will be appreciated
You can push an argument-bound version of the anonymous function by using .bind() thereby ensuring the value of the first argument.
var d = [];
for (var i = 0; i < 3; i++) {
d.push((function(i) {
console.log('iterator: ' + i);
}).bind(this, i));
}
d[0](); // iterator: 0
d[1](); // iterator: 1
d[2](); // iterator: 2
Here's my solution:
var d = [];
function newFunction(i) {
var k = i;
return function() {
console.log('iterator: ' + k);
}
}
for (var i = 0; i < 3; i++) {
d.push(
newFunction(i)
);
}
d[0]();
d[1]();
This works because when you create the new function, it creates a variable scope that doesn't change with your for loop. It saves the state of i on each iteration so that when you call them again, it runs with the i it was originally declared with.

Get stuck in realizing the Closure feature (javascript) [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
As a javascript beginner, I thought I realized the Closure feature until seeing the trap from the sample code below. I tried to modify the code to pop up the result which many C/C++/C#/Java programmers expect as,
"item1 1"
"item2 2"
"item3 3"
After one hour struggling, I still can't do that. Could any javascript master teach me how to modify it ? Thank you.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
The idea is to create a closure inside which it will be fixed.
You can nest the for content inside an immediate function :
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
(function(k){// k will equals i
var item = 'item' + list[k];
result.push( function() {alert(item + ' ' + list[k])} );
})(i);// i in argument
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
for (var j = 0; j < fnlist.length; j++) fnlist[j]();
}
testList();
It is the same as writing an other function :
function doStuff(k,arr,arr2){
var item = 'item' + arr[k];
arr2.push( function() {alert(item + ' ' + arr[k])});
}
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) doStuff(i,list,result);
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
for (var j = 0; j < fnlist.length; j++) fnlist[j]();
}
testList();
With the recent forEach(value, index, array) method the closure is automatically provided :
function buildList(list) {
var result=[];
list.forEach(function(e){
result.push(function(){alert('item'+e+' '+e)});
});
return result;
}
buildList([1,2,3]).forEach(function(e){e();});
Rather cool.
Using ECMAScript 6 let (thanks elad.chen)
let can replace var to scope i inside functions in the loop : no more need to create a closure as in (1) and (2). You will just have to move the item+list[i] part inside the function. However let is available in strict mode only, and not in all browsers yet (it still misses firefox for example)
'use strict'
function buildList(list) {
var result = [];
for(let i=0;i<list.length;i++) {
result.push(function(){alert('item'+list[i]+' '+list[i])});
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
for (var j = 0; j < fnlist.length; j++) fnlist[j]();
}
testList();
Not master by any means, just learning, too:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( (function() {alert(item + ' ' + list[i])}) ());
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
}
testList();
This could be good chance for IIFE usage. Demo: http://jsfiddle.net/zh02a69j/2/
In the moment when you call your first function by fnlist[j](), your i value is last incremented value. Your function which push results to array 'see' just that value, since it is not executed immediately, in loop - it is defined, but not executed. You can make it execute immediatelly (as IIFE), in loop, then script returns expected output.
If i made some mistake (to all previous and future downvoters:)) - please guide me, too.

JS - Push functions into an array and name them

I'm trying to add functions into an array. These have to be named 'opdracht1' through to 'opdracht10'.
Though, I cannot figure out how to give it a name.
var opdrachtArray = [];
for (i = 0; i < 10; i++) {
opdrachtArray.push(function() {func(i); });
}
It adds the functions but as I said earlier I cannot find out how to add a name.
Also, am I later just able to define the functions and call them when I need them?
Name your functions by placing them on the window object:
for (i = 0; i < 10; i++) {
f = function() { func(i); }); // but see below
window['opdracht' + i] = f
opdrachtArray.push(f);
}
However you have a more basic problem. All your functions close over i and therefore func is always going to be called with the value of i after the loop finishes, in other words, 10. One solution is:
function make_func(i) {
return function() {
return func(i);
};
}
then
for (i = 0; i < 10; i++) {
f = make_func(i);
window['opdracht' + i] = f;
opdrachtArray.push(f);
}
Or
for (i = 0; i < 10; i++) {
(function(i) {
var f = function() { func(i); };
window['opdracht' + i] = f
opdrachtArray.push(f);
}(i));
}
or just use func.bind(null, i), which does approximately the same thing.
for (i = 0; i < 10; i++) {
f = func.bind(null, i);
window['opdracht' + i] = f;
opdrachtArray.push(f);
}
If you want each function to be assigned to a name, use dictionary, not array:
var opdrachtDict = {};
for (i = 0; i < 10; i++) {
opdrachtDict["opdracht"+i]=func.bind(null,i);
}
function func(i){
alert(i);
}
opdrachtDict["opdracht3"](); //ok, lets call it and see if you think its still a wrong answer ;)...
You could store the name and function in an object and push each object to the array:
var opdrachtArray = [];
for (i = 0; i < 10; i++) {
var name = 'opdracht' + (i+1);
opdrachtArray.push({name : name, callback : function() {func(i); }});
}
Well a function defined in the global scope just ends up being a property of self/window anyways. i.e.:
function mr_function(){return 5;}
self["mr_function"];
window["mr_function"];
Both (property of self / window) reference the function we defined. I guess you could name your function that way if you're careful. Or assign them to some other object's property if you'd rather not make them global.

Categories