How To Control Variable Hoisting? [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 10 years ago.
I have a problem with binding functions from an array to some elements, but i get this error after i click on any element with class "class".
Uncaught TypeError: Property '4' of object function (){alert(1)},function (){alert(2)},function (){alert(3)},function (){alert(4)} is not a function
var c = [
function(){alert(1)},
function(){alert(2)},
function(){alert(3)},
function(){alert(4)}
];
function test(b){
for(var i = 0; i < b.length; i++){
$('.class').eq(i).bind('click', function(){
b[i]();
});
}
}
test(c);
I think that the variable i keep its last value.
any solution or explanation will be really appreciated.
Thanks in advance.

You need a closure - in js variable scope is determined by functions. With jQuery, just use .each:
$('.class').each(function(i) {
$(this).on('click', function(){
alert(i+1);
});
});
However, if you really need to have an array of distinct functions you just can bind the functions themselves directly as handlers:
var elems = $('.class');
for (var i=0;i<b.length;i++) {
elems.eq(i).on('click', b[i]);
}

Your function array only has 4 items, so b[4] is undefined. Instead of referencing b inside your click handler, make it the click handler itself:
elems.eq(i).on('click', b[i]);
By doing this the value of i will be examined each iteration through the loop, instead of when the element is clicked (at which point its value should always be 4).

The problem is, in your loop the functions you are setting as click handlers are all sharing the same i value. After the loop, that i value will be 4, so each function will try to call b[4], which doesn't exist.
You need to create a new scope for each of the functions, so that the i values are all different.
for(var i = 0; i < b.length; i++){
(function(i){
$('.class').eq(i).bind('click', function(){
b[i]();
});
}(i));
}
You can also just set the function from the array as the click handler directly.
for(var i = 0; i < b.length; i++){
$('.class').eq(i).bind('click', b[i]);
}

Related

event assignment using improper iterator value [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I am attempting to create a closure in a loop in order to set an event listener with my iterator value. However, the value in setCookie is not properly being set. What am I doing wrong here?
obj = document.getElementsByClassName("class");
l = obj.length;
i = 0;
function addEv() {
while (i < l) {
obj[i].addEventListener("click", listener.bind(null, i));
function listener(index) {
setCookie("item", index, 24)
}
i++;
}
}
UPDATED ANSWER
On way to capture iterating values is by using immediately invoked functions to capture the value in a closure.
while (i < l) {
obj[i].addEventListener("click", (function(index) {
return function(ev) {
//setCookie("item", index, 24)
console.log(index)
};
}(i)));
i++;
}
This example will wrap the iterating i as index.

How to define an array of function, when the array is big [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I want to define an array of function so I tried the following code, but it does not has the intended result, because the i in the loop cannot be retrieved else where.
<script>
var f = []
for (var i=0; i<1000; i++){
f[i] = function(){
return i
}
}
console.log(f[3]);
</script>
There is the brute-force method to write 1000 lines of codes to define function, is there other ways?
In fact I met this problem in Java, Array of function pointers in Java
, so answers in both Java or JS would be helpful.
Use an immediately executed function inside the loop to create a scope, so that you can have one variable for each function instance:
var f = []
for (var i=0; i<1000; i++){
(function(i){
f[i] = function(){
return i;
}
})(i);
}
console.log(f[3]());
demo: http://jsfiddle.net/Guffa/rPKss/
It's a closure / scope issue, you have to lock in the value of i
<script>
var f = [];
for (var i=0; i<1000; i++){
(function(j) {
f[j] = function(){
return j;
}
})(i); // passing as argument to IIFE makes it local to that scope
}
console.log(f[3]);
</script>

TypeError: this.reduce is not a function [duplicate]

This question already has answers here:
Loop (for each) over an array in JavaScript
(40 answers)
Closed 3 years ago.
After adding a method to the Array prototype, some other, unrelated script breaks.
[Opera] Unhandled Error: 'this.reduce' is not a function
[Firefox] TypeError: this.reduce is not a function
The method itself works ([1,2,3].xintsum() outputs 6 as expected).
// adding a function to the Array prototype
Array.prototype.xintsum = function() { return this.reduce(function(old, add) {return old + add;}, 0); };
// accessing the array in a way that worked before
$(document).ready(function (){
var some_array = [];
for (head_n in some_array) {
var v = some_array[head_n];
$('<th></th>').text(v);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
This is happening because you are using for..in on an array. You shouldn't be doing that.
When you added Array.prototype.xintsum, you added a xintsum property to every array. So, what happened was, that your for loop iterated over that property of your array.
The value of this property is a function. When you pass a function to .text(), jQuery will call it like this:
v.call(ele, index text);
It's setting this to the element. And well, DOMElements don't have .reduce functions.
You need to loop like this:
for(var i = 0; i < some_array.length; i++){
var v = some_array[i];
}
This code:
var v = some_array[head_n];
$('<th></th>').text(v);
when it gets to xintsum is the same as this:
$('<th></th>').text(function() {
return this.reduce(function(old, add) {
return old + add;
}, 0);
});
When a function is passed to text, the function is called once for each element contained in the jquery object which it is called on. For each call this refers to that dom element. In this case, the th you have created. Therefore, the error message is letting you know that th has no such function.
v() is being called in the context of the <th> element, not an array. Hence, the <th> doesn't have a method reduce(). This is because for ... in iterates over non-numeric properties just as much as numeric properties. I would recommend using some_array['head'].forEach() instead.

var scope in javascript functions

I have the following small code snippet with the following expected and real output. My question is quiet simple. Why is it printing it this sequence? and how to I print the expected output?
Gr,
expected result:
0
1
2
0
1
2
real result:
0
1
2
3
3
3
this is the code:
var functions = [];
for (var i=0; i<10; i++) {
console.log (i);
functions.push (function () {
console.log (i);
});
};
for (var j=0; j<functions.length; j++) {
functions[j] ();
};
The functions that you push into the array doesn't log the value of i as it was when the function was created, they log the value of i at the time that the function is called.
Once the first loop ends, the value of i is 10, therefore any of the functions called after that will log the value 10.
If you want to preserve the value of i at different states, you can use a closure to make a copy of the value:
for (var i=0; i<10; i++) {
console.log (i);
(function(){
var copy = i;
functions.push (function () {
console.log (copy);
});
})();
};
The local variable copy will get the value of i and retain the value. You can also pass the value as a parameter to the function:
for (var i=0; i<10; i++) {
console.log (i);
(function(copy){
functions.push (function () {
console.log (copy);
});
})(i);
};
The expected result should be:
1
2
...
10
10
10
... 7 more times
The reason for this is simple. The console.log(i) inside your loop is correctly printing the value of i at each iteration of the loop. When you create and push a function into the functions array, what you're doing is closing each of those functions over the same variable i. At the end of your loop, i no longer satisfies your loop condition, so i = 10 is true. As a result, since each of those functions is going to execute console.log(i), and they're each closed over the same i, which now has value 10, you should expect to see the value 10 printed 10 times.
To prevent this, you will want to make a function which returns a function rather than creating functions directly in a loop:
var functions = [], i, j;
function createEmitter(i) {
return function () {
console.log(i);
};
}
for (i = 0; i < 10; i++) {
console.log(i);
functions.push(createEmitter(i));
};
for (j = 0; j < functions.length; j++) {
functions[j]();
};
Now, each of those created functions is closed over its own private scope variable, which resolves the problem.
You should update your code example to be i < 3 so that your results and function match up.
The functions you push into the functions array are storing a reference to the variable i, which after executing the top loop, is 10. So when it executes, it will go get the variable i (which is 10) and print that 10 times.
Here's a good way to see this in action:
for (var i=0; i<10; i++) {
console.log (i);
};
console.log(i) //=> 10
When you are using a variable, remember that the variable can change, it's not frozen at it's current value. You are only holding on to a reference to something else.
To fix this problem, I would run this type of minor refactor on the code (because the other answers have already created an extra scope, figured I'd give you something different). Rather than storing 10 functions, just store the numbers and execute them with a single function. This is a more elegant way to write it anyway, and takes up less space. I'm sure this example was abstracted from whatever code was really giving you the problem, but the general pattern still applies.
numbers = [];
for (var i=0; i<10; i++) {
console.log (i);
numbers.push(i);
};
numbers.forEach(function(i){
console.log(i);
});

How are local variables referenced in closures? [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I am reading an article (JavaScript Closures for Dummies) and one of the examples is as follows.
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();
When testList is called, an alert box that says "item3 undefined". The article has this explanation:
When the anonymous functions are called on the line fnlist[j](); they all use the same single closure, and they use the current value for i and item within that one closure (where i has a value of 3 because the loop had completed, and item has a value of 'item3').
Why does item have a value of 'item3'? Doesn't the for loop end when i becomes 3? If it ends shouldn't item still be 'item2'? Or is the variable item created again when testList calls the functions?
You're close...
Why does item have a value of 'item3'? Doesn't the for loop end when i becomes 3?
Yes.
If it ends shouldn't item still be
'item2'?
Nope. This example is a little tricky. During the last iteration of the loop, i is 2, but it references the 3rd element of the list array, which is 3. In other words, item == 'item' + list[2] == 'item3'
Or is the variable item created again when testList calls the functions?
No, you were almost right the first time. I think you just missed that item[2] has the value of 3.
The for loop within buildList completes before you do the following:
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
... therefore, by that time (when you call each function), the variable item will be whatever was last assigned to it (i.e. "item3"), and i will be 3 (as a result of the last i++ operation), and list[3] is undefined.
It's all to do with the fact that the loop completes before you call the closure'd function. To prevent this, you could create a new closure, like so:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push(
(function(item, i){
// Now we have our own "local" copies of `item` and `i`
return function() {
console.log(item + ' ' + list[i])
};
})(item, i)
);
}
return result;
}
I think the point you are missing is that list[i] is underfined because i is 3, and list is only defined for 0..2.
The list variable is stored in closure as you say.
Actually you can access the list variable, but you are trying to access list[3]. After all, the i variable is also stored as a closure and it's value is 3 when the console.log function is called.
The loop ends when i becomes 3, but the "item" variable stored in the closure, and displayed by alert, is set to
var item = 'item' + list[i];
the text 'item' + the value at list[2]. The third list item is 3, so the text is item3

Categories

Resources