JS - Push functions into an array and name them - javascript

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.

Related

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

Is this the right equivalent for my loop?

I wrote a simple javascript code. My for loop iterates a "let" declared variable, i between 0 and 2. A function gets declared within the loop only when i == 2. The function has to return the value of i variable. When I call this function from outside the loop, the function returns the value of i = 2 (which is natural for a block scope variable i. However, when I rewrite the loop code as its non-loop equivalent code-block, the function (still called from outside the block) returns the vale of i = 3. What is going on?
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo()); //returns 2
// loop equivalent
{
let i = 0;
i = 1;
i = 2;
printNumTwo = function() {
return i;
}
i = 3;
}
console.log(printNumTwo()); // returns 3
Your example is bad because your loop is not counting after 2. So If your loop looks like i <= 3:
for (let i = 0; i <= 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
You would get exactly same result as your non-loop example and that's because of closure in javascript but return breaks for loop. Your function is saving reference to that variable from outside scope.
It's because you're actually setting the function to return the value 3 because of the non-loop environment. You should change the loop a little, adding another variable, but first make your function look like this:
printNumTwo = function() {
return num;
}
And in your simulated loop:
i = 2;
num = i;
printNumTwo = function() {
return num;
}
i = 3;
In your non loop based code, printNumTwo is not executed at the same point of its declaration and so the value of i is updated before it is executed so the value 3 is returned.
{
let i = 0;
i = 1;
i = 2;
printNumTwo = function () {
return i;
}
i = 3;
}
console.log(printNumTwo());
but if you run the following code, it should print 2 since it is executed before value if i is set to 3
{
let i = 0;
i = 1;
i = 2;
printNumTwo = (function() {
console.log(i);
})()
i = 3;
}
Note: return in for loop breaks the further execution of the loop, so even if your first code had i <= 3 as its breaking condition, it will return 2.
for (let i = 0; i <= 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo())
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
printNumTwo = function (i) {
// when references 'i' in this function, 'i' goes to the global scope.
return i;
};
// set the value 3 for 'i' in the global scope
i = 3;
}
console.log(printNumTwo()); // return 3;
try this
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
printNumTwo = function (i) {
return i;
}.bind(null, i); // you set the current value as parameter = 0
i = 3; // i = 3 and break loop
}
console.log(printNumTwo()); // return 0;
try this
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
let i = 0;
i = 1;
i = 2;
printNumTwo = function (i) {
return i;
}.bind(null, i); // you set the current value as parameter = 2
i = 3; // i = 3 and break loop
}
console.log(printNumTwo()); // return 2;
I appreciate all the answers I got to my question. All pointing to the case of how a function, when called, handles the environments in which it was both called and created. I read this useful explanation in the book "Eloquent JavaScript" and think it would be good to share it,
"A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called."
~ Eloquent_JavaScript/Closure

undefined in Closures In For Loops

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]);

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.

Categories