Issue getting closures to work [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I have a piece of code that I'm trying to have alert 1,2,3. I'm having issues using closures properly, so I can't figure this out.
The original code:
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();
I am trying to do something like this to buildList() to get it to work properly:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result[i] = function(x) {
result.push( function() {alert(item + ' ' + list[x])} );
}(i);
}
return result;
}
I know I'm making mistakes on working with the closures, I'm just not sure what the problem is.

Your second try was closer to the solution but still doesn't work because your inner-most function is capturing variable item from your top-level function: item is just always referencing the same instance, which was created when calling buildList().
var scope in JavaScript is always bound to current function call, not to code block, so it's not bound to control statements like for.
For that reason, the alerts likely show the value 'item' + (list.length-1) had at the time of calling buildList().
Since you are passing i to your closure, you should declare var item within that function, e.g:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
result[i] = function(x) {
// x and item are both local variables of anonymous function declared just above
var item = 'item' + list[x]; // or maybe you meant 'item' + x?
return function() {alert(item + ' ' + list[x])};
}(i);
}
return result;
}
Note that the closure would still capture a reference to list so will display the value it contains at the time of calling functions in the array returned by buildList(). Also local variable item is completely optional, you could call alert('item' + x /*or is it list[x]?*/ + ' ' + list[x]).

From How do JavaScript closures work?
Note that when you run the example, "item2 undefined" is alerted three
times! This is because just like previous examples, there is only one
closure for the local variables for buildList. When the anonymous
functions are called on the line fnlistj; 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 'item2'). Note we are indexing from
0 hence item has a value of item2. And the i++ will increment i to the
value 3.
You need to make a closure in each loop iteration if you are to store the matching value of i:
function buildList(list) {
var result = [], item, closure;
for (var i = 0; i < list.length; i++) {
item = 'item' + list[i];
// call this function with the string you wish to store
// the inner function will keep a reference to the 'msg' parameter even after the parent function returns
closure = (function(msg) {
return function() {
alert(msg);
};
}(item + ' ' + list[i]));
result.push( closure );
}
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();
Same question asked here and here. Same answers here, here, here, here and probably in dozen more places.

Related

String in array showing up as undefined, despite being console.log-ed successfully a few lines earlier

In the for loop below, when I console.log searchTermsList[i] the first time (console.log('searchTermsList[i] is:' + searchTermsList[i]);), it works correctly and prints out the respective string.
However, when I do it again later in the code (console.log('the searchTermsList[i] Im about to use for the query is:' + searchTermsList[i]);), it prints out that it's undefined. Both console.logs are within the same loop, so why isn't the 2nd one able to find the value?
for (var i = 0; (i < top3List.length) && (i < searchTermsList.length); i++){
console.log('searchTermsList[i] is:' + searchTermsList[i]);
console.log('top3List[i] is:' + top3List[i]);
var MCI_Results = Parse.Object.extend("MCI_Results");
var MCI_Results_Comparison_Query = new Parse.Query(MCI_Results);
// Compare respective items' MCI_Results array to eBay results (top3List[i])
MCI_Results_Comparison_Query.equalTo('parent', user);
MCI_Results_Comparison_Query.contains('searchTerm', searchTermsList[i]);
MCI_Results_Comparison_Query.containsAll('Results', top3List[i]);
MCI_Results_Comparison_Query.find()
.then(function(results) {
// No new items, Results and top3List[i] are identical
if (results.length > 0) {
console.log('done updating channel');
}
// New items found, Results and top3List[i] don't match.
else {
console.log('no matching MCI_Results, lets push some new shit');
// Find MCI_Results object for specific item
var MCI_Results_Update_Query = new Parse.Query(MCI_Results);
MCI_Results_Update_Query.equalTo('parent', user);
console.log('the searchTermsList[i] Im about to use for the query is:' + searchTermsList[i]);
MCI_Results_Update_Query.contains('searchTerm', searchTermsList[i]);
// Update MCI_Results with new top3List eBay results
MCI_Results_Update_Query.find()
.then(function(results) {
console.log('totally just updated the MCI_Results, NBD');
})
.then(function() {
// Check for high priority MC items
});
}
});
}
i is a mutable variable. i++ will change i to point to a different index by the time that function is called.
You'll need to create a new variable in a new scope, possibly using an immediately-invoked anonymous function, and not change it.
An example:
var makeBadAdders = function(n) {
var adders = []
for (var i = 0; i < n; i++)
adders[i] = function(x) {
// Closes over a mutable variable.
// Function will use the most up-to-date value of i.
return i + x;
}
return adders
}
var badAdders = makeBadAdders(3);
console.log(badAdders[1](1)); // 4
console.log(badAdders[2](1)); // Also 4
var makeAdders = function(n) {
var adders = []
for (var i = 0; i < n; i++)
adders[i] = makeAdder(i);
return adders
}
var makeAdder = function(i) {
// Closes over an effectively immutable variable (i).
return function(x) {
return x + i;
}
}
var goodAdders = makeAdders(3);
console.log(goodAdders[1](1)); // 2
Note that you could write makeAdder inline like so:
adders[i] = (function(i) {
return x + i
})(i)
(This shadows the outer, mutable i.)
But usually, it's better to just avoid mutable variables and use something like forEach instead. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)

javascript closure in loop

Following code is given:
var a = [ ], i = 0, j = 0;
for (i = 0; i < 5; i += 1) {
(function(c) {
a.push(function () {
console.log(c); });
})(i);
};
for (j = 0; j < 5; j += 1) { a[j](); }
Why does i always get bigger by 1 instead of staying 5? Hasn't the foor loop already been passed, so the i parameter given to the anonymous function should be 5?
If you referenced i from the inner closure then yes, you would see the result being 5 in all cases. However, you pass i by value to the outer function, which is accepted as parameter c. The value of c is then fixed to whatever i was at the moment you created the inner closure.
Consider changing the log statement:
console.log("c:" + c + " i:" + i);
You should see c going from 0 to 4 (inclusive) and i being 5 in all cases.
chhowie's answer is absolutely right (and I upvoted it), but I wanted to show you one more thing to help understand it. Your inner function works similarly to a more explicit function call like this:
var a = [ ], i = 0, j = 0;
function pushFunc(array, c) {
array.push(function () {
console.log(c);
});
}
for (i = 0; i < 5; i += 1) {
pushFunc(array, i);
}
for (j = 0; j < 5; j += 1) { a[j](); }
Which should also help you understand how c comes from the function argument, not from the for loop. Your inner function is doing exactly the same thing as this, just without an externally declared named function.

JavaScript global var scope issue [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
How do I return the response from an asynchronous call?
(41 answers)
Closed 8 years ago.
I can't figure out why specialArray is not keeping its values outside of $.getJSON. I thought I understood scope, any help appreciated. It spits out values to the console, but loses the values once it gets outside .getJSON. Any ideas?
var specialArray = [];
var data, temp, regionArrayNumber;
var numberOfRegions = 29;
var chartData = [];
$(document).ready(function() {
// set up array of objects, organized by region_id
for (var j = 0; j < numberOfRegions; j++) {
temp = {
"region_id" : (j + 1),
"number_of_reads" : 0,
"bus_count" : 0,
"reads_per_bus" : 0
};
chartData.push(temp);
}
$.getJSON('https://data.cityofchicago.org/resource/historical-traffic-congestion-region.json', function(data) {
// cycle through objects, add numbers to totals
for (var i = 0; i < data.length; i++) {
regionArrayNumber = data[i].region_id - 1; // get region id, offset because of zero-based array
chartData[regionArrayNumber].bus_count += parseInt(data[i].bus_count);
chartData[regionArrayNumber].number_of_reads += parseInt(data[i].number_of_reads);
}
// calculate avg reads per bus
for (var k = 0; k < chartData.length; k++) {
chartData[k].reads_per_bus = (parseInt(data[k].number_of_reads)) / (parseInt(data[k].bus_count));
}
// set up array for google chart
for (var x = 0; x < chartData.length; x++) {
var tempArray = [];
tempArray[0] = chartData[x].region_id;
tempArray[1] = parseInt(chartData[x].number_of_reads);
specialArray.push(tempArray);
console.log("Inside: " + specialArray[x][0] + ", " + specialArray[x][1]);
}
});
console.log("Outside: " + specialArray[1][0]);
}); // end of doc.ready
The good news is that you understand scope just fine :)
getJSON is an asynchronous function... that means it will kick off the service call, move on with the next statement (which is console.log("Outside: " + specialArray[1][0]);) and then when the service call completes it will get around to invoking your callback function (what you called 'inside').
If you want to act on the result of the service call, that code needs to either live inside the callback function or be invoked by something inside the callback function.
The reason getJSON and similar APIs are like this is because they want to make sure your code doesn't 'hang' and make the browser unresponsive while waiting for something that could take a while to complete. Kinda tricky to wrap your head around at first but it's doing you a favor.

Javascript string syntax and for loop logic

So i am learning how to perform loops with javascript, and was researching how to do it with an array. I understand how to create arrays, but what i am not clear on is using implementing that in the loop. So i cam across examples that kind of "manufacture an array within the loop" as i think i have done in this case.
What i want to do is use javascript to change the classes of different dom elements on a page. What i don't want to do is repeat the same code over and over again with a different numerical value. I thought i had everything right but apparently i don't. Here is my code:
<script>
for (var i = 0; i < 11; i++) {
var storyImageSubmit + [i] = document.getElementById('story_image_' + [i]);
var realImageUpload + [i] = document.getElementById('realImageUpload' + [i]);
realImageUpload + [i].addEventListener('mouseover', over_profile_image_submit_ + [i], false);
realImageUpload + [i].addEventListener('mouseout', out_profile_image_submit_ + [i], false);
realImageUpload + [i].addEventListener('mousedown', down_profile_image_submit_ + [i], false);
realImageUpload + [i].addEventListener('mouseup', up_profile_image_submit_ + [i], false);
function over_profile_image_submit_ + [i] + () {
storyImageSubmit + [i].className = "accountSettingsBrowseSubmitHover";
}
function out_profile_image_submit_ + [i] + () {
storyImageSubmit + [i].className = "accountSettingsBrowseSubmit";
}
function down_profile_image_submit_ + [i] + () {
storyImageSubmit + [i].className = "accountSettingsBrowseSubmitDown";
}
function up_profile_image_submit_ + [i] + () {
storyImageSubmit + [i].className = "accountSettingsBrowseSubmit";
}
}
</script>
What i want the code to look like, but iterated with the different numerical values of 1-10, is this:
<script>
for (var i = 0; i < 11; i++) {
var storyImageSubmit1 = document.getElementById('story_image_1');
var realImageUpload1 = document.getElementById('realImageUpload1']);
realImageUpload1.addEventListener('mouseover', over_profile_image_submit_1, false);
realImageUpload1.addEventListener('mouseout', out_profile_image_submit_1, false);
realImageUpload1.addEventListener('mousedown', down_profile_image_submit_1, false);
realImageUpload1.addEventListener('mouseup', up_profile_image_submit_1, false);
function over_profile_image_submit_1() {
storyImageSubmit1.className = "accountSettingsBrowseSubmitHover";
}
function out_profile_image_submit_1() {
storyImageSubmit1.className = "accountSettingsBrowseSubmit";
}
function down_profile_image_submit_1() {
storyImageSubmit1.className = "accountSettingsBrowseSubmitDown";
}
function up_profile_image_submit_1() {
storyImageSubmit1.className = "accountSettingsBrowseSubmit";
}
}
</script
what am i doing wrong here?
<----------------------UPDATE:----------------------->
this is my code presently, after determining that i need an array to accomplish what i want to do. I tested my loop of my array variable, and everything in that department seems to be working fine.
The next issue i have run into now is getting javascript not to rewrite over my listening variables defined by each iteration. I decided the best way to do that would be to eliminate any variables in the loop so that each listening and function execution is unique. I am doing that with the assumption that rewriting my variables is why it wont work. but even after doing that, it won't work.
<script>
var storyImageValue = ["1","2","3","4","5","6","7","8","9","10"];
for (var i = 0; i < storyImageValue.length; i++) {
document.getElementById('realImageUpload' + storyImageValue[i]).addEventListener('mouseover', function () { document.getElementById('storyImageSubmit' + storyImageValue[i]).className = "accountSettingsBrowseSubmitHover"; }, false);
}
Thoughts?
Try something like this:
for (var i = 1; i <= 10; i++) {
var storyImage = document.getElementById('story_image_' + i);
var realImage = document.getElementById('realImageUpload' + i);
realImage.addEventListener('mouseover', function () { storyImage.className = "..."; }, false);
...
}
In JQuery you can use below syntax for parsing javascript array :
$.each(curVal, function(i, array){
alert (curVal);
});
If you have an array in JavaScript, this is how you can iterate it with a for-loop construct:
var arr = [1, 2, 3], i, element;
for (i = 0; i < arr.length; ++i) {
element = arr[i];
// Now do whatever you want with element within this loop.
}
Update as per your comment:
What's happening in the code you have in the comment is that the variable i is not being scoped properly for your purposes. Thing is, in JavaScript, there's no block scope, only function scope...
This means that whenever your event listener function is being invoked, it's getting the value of i but that value is always going to be the last value that was used in the loop..in this case, 10.
Try it like this:
var storyImageValue = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
for (var i = 0; i < storyImageValue.length; i++) {
(function (index) {
document.getElementById('realImageUpload' + storyImageValue[index]).addEventListener('mouseover', function () {
document.getElementById('storyImageSubmit' + storyImageValue[index]).className = "accountSettingsBrowseSubmitHover";
}, false);
}(i));
}
What I'm doing is I'm creating a function that takes a single argument (representing the loop index in our case) which invokes itself immediately after being declared, passing in the current value of the loop counter i.
This is because of what I mentioned before as regards scoping. Since JavaScript does not support block scoping, but only function scoping, creating a function which immediately invokes itself will create the scope you need to store the loop counter so that once your event listener function is executed, it will access the correct value.

callback function within a loop

I am really struggling with concept of scope in my code.
I am simply trying to create a 'callback' function which will add a className to a variable. As it's inside a function, I am passing the global variable as parameters to the callback function using the concept of closure (still dont understand how closure works).
var ePressCuttingsArray = $(".cPressCuttings");
var eSelectedPressCuttingsArray = [];
var iIndexArray = [];
for (var i = 0; i < 7; i++) {
var iIndexArrayValue;
// two conditions being checked in while loop, if random no. is not in global array (iIndexArray) & i var is equal to eSelectedPress... array
while (jQuery.inArray(((iIndexArrayValue = Math.floor(Math.random() * 14) + 1), iIndexArray) === -1)
&& (i === eSelectedPressCuttingsArray.length))
{
// to push a value at a position from array ePressCut... into eSelectedPress... array
eSelectedPressCuttingsArray.push(ePressCuttingsArray[iIndexArrayValue]);
// run a function to addClass to the recently pushed value in eSelectedPress... array
(function (i) {
$(eSelectedPressCuttingsArray[i]).addClass("cPressCuttingsDisplay0" + i)
} (i) );
iIndexArray.push(iIndexArrayValue);
}
}
Could someone explain why the closure func. is not performing correctly, i.e. it always successfully add the className "cPressCuttingsDisplay00", but doesnt follow that up with a className of "cPressCuttingsDisplay01" for the next loop iteration.
You should be able to accomplish your goal by using a for loop:
var ePressCuttingsArray = $(".cPressCuttings").makeArray();
var eSelectedPressCuttingsArray = [];
for (var i = 0; i < 7; i++) {
var idx = Math.floor(Math.random() * ePressCuttingsArray.length);
var selectedItem = ePressCuttingsArray[idx];
selectedItem.addClass('cPressCuttingsDisplay0' + i);
eSelectedPressCuttingsArray.push(selectedItem);
ePressCuttingsArray.splice(idx, 1);
}

Categories

Resources