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);
}
Related
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.
I have the following function that can pull out a random index from an array without repeating the index and keeps pulling them out until all have been used and then resets itself and starts re-using them. It also tries to make sure that the last one that was pulled out isn't the same as the next one pulled out on the reset so that you don't ever have the same index come out in a row.
var listIndexes = [];
var lastIndex;
function getRandomIndex(indexes)
{
if (!listIndexes.length) {
for (var i = 0; i < indexes; i++) {
listIndexes.push(i);
}
}
var randomIndex = Math.floor(Math.random() * listIndexes.length);
var uniqueIndex = listIndexes[randomIndex];
listIndexes.splice(randomIndex, 1);
if(lastIndex && uniqueIndex == lastIndex)
{
listIndexes = [];
getRandomIndex(indexes);
return;
}
lastIndex = uniqueIndex;
return uniqueIndex;
}
var index = getRandomIndex(5);
console.log(index);
However when it hits the code: if(lastIndex && uniqueIndex == lastIndex) it causes it to return undefined for the index. So the way I'm trying to exit the function and re-call the function to try again isn't working as planned.
How can I exit the current function call and re-call the function to get a new random index that isn't the same as the lastIndex. Note that the lastIndex is kept intact until the new index isn't the same regardless of how many times the function is called.
Try this:
if(lastIndex && uniqueIndex == lastIndex)
{
listIndexes = [];
return getRandomIndex(indexes);
}
just change your empty return to run the function again:
return getRandomIndex(indexes);
The approach of rethrowing the die when you don't get the result you want is not optimal and when the random number isn't so random (tends to repeat the same number), then you're cycling unnecessarily.
Try this:
function RandEleGenerator(list) {
var lastChosen;
var currentList = list.slice();
function randomIndex() {
return Math.floor(Math.random() * currentList.length);
}
return function() {
// Choose element
var index = randomIndex();
var obj = currentList[index];
// Remove it from current list
currentList.splice(index, 1);
if(currentList.length == 0) {
// If empty, restore list
currentList = list.slice();
// But not without removing last chosen element
index = currentList.indexOf(obj);
currentList.splice(index, 1);
}
return obj;
};
}
Usage:
var reg = new RandEleGenerator([1,2,3]);
reg(); // 3
reg(); // 2
reg(); // 1
reg(); // 2
reg(); // 3
reg(); // 2
reg(); // 1
The chosen element is removed from the list so it cannot be rechosen. In order to guarantee that a value isn't repeated when the list ends, the list is recreated and the last element chosen is removed from the list immediately. The process then continues randomly choosing elements to remove from the list.
Edit:
In order to say, generate an array to pass to RandEleGenerator, the following code is sufficient:
var arrToPass = [];
for (var i = 0; i < 3; i++) {
arrToPass.push(i);
}
var reg = new RandEleGenerator(arrToPass);
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)
I'm trying to loop through an array of images but can't seem to get past image 2.
The array should also loop back to 1 when the last image has passed...
var WorkArray = new Array('work/01.png', 'work/02.png', 'work/03.png', 'work/04.png');
var nelements = WorkArray.length;
preload_image_object = new Image();
var i = 0;
for(i=0; i<=nelements; i++) {
preload_image_object.src = WorkArray[i];
}
function cC() {
var nelements = WorkArray.length;
var i = 0;
for(i=0; i<=nelements; i++) {
nelements = WorkArray[i];
}
document.getElementById("work").style.backgroundImage="url('"+WorkArray[i]+"')";
}
You can save the current file and use modulo to run in cyclic manner.
It will look something like that:
var WorkArray = new Array('work/01.png', 'work/02.png', 'work/03.png', 'work/04.png');
var currentImage = 0
function nextImage(){
currentImage = (currentImage + 1) % WorkArray.length;
document.getElementById("work").style.backgroundImage="url('"+WorkArray[currentImage]+"')";
}
You are overwriting nelements with the current element of the loop:
nelements = WorkArray[i];
The following should fix your loops:
var WorkArray = new Array('work/01.png', 'work/02.png', 'work/03.png', 'work/04.png');
var preload_image_object = new Image();
/* Lets get rid of `nelements`, as its just confusing. Get the length here.
* If, for performace reasons you want to use elements, the best way is to reverse
* aka for(var i = WorkArray.length-1; i >= 0 ; i--)
* Also, its simpler to declare the var in your for-loop itself instead of outside of it.
*/
for(var i = 0; i <= WorkArray.length; i++){
preload_image_object.src = WorkArray[i];
}
Also, again for simplifications sake, your application of the background-image could be done inside your for loop as well, and can be made to look cleaner with some spaces and omitting the ' inside your url():
document.getElementById("work").style.backgroundImage = "url(" + WorkArray[i] + ")";
I have a problem with this script, something is going wrong.
Rnumer stays undefined.This script should return and write all uneven digits from the random number list. Can someone tell me what I do wrong. Thanks in advance
var Rnumber = new Array();
for (i = 0; i<= 100;i++)
{
Rnumber[i] = Math.ceil(Math.random()*101);
// document.write(Rnumber[i] + "<br/>");
}
function unevenAndDivisible(Rnumber)
{
var remainder = new Array();
for (i = 0; i<= 100; i++)
{
remainder = parseInt(Rnumber[i])%2;
}
return remainder;
}
document.write(unevenAndDivisible());
Changed to
var Rnumber = new Array();
for (i = 0; i<= 100;i++)
{
Rnumber[i] = Math.ceil(Math.random()*101);
// document.write(Rnumber[i] + "<br/>");
}
function unevenAndDivisible(Rnumber)
{
var remainder = new Array();
for (i = 0; i<= 100; i++)
{
remainder[i] = Rnumber[i]%2;
}
return remainder;
}
document.write(unevenAndDivisible(Rnumber));
but now i get the result :
0,1,0,0,1,0,0,0,1,1,0,0,1,0,1,1,1....
I simply want maybe I asked it wrong the first time, to write al uneven numbers from the random list of Rnumbers
Then I need to divide that through 7 and return that.
EDIT
Allmost all problems are clear , thanks everyone for that.
Their is still one question left:
In this code below it only take the first uneven value from remainder and I want that it takes all values that are uneven to the next if statement to check %7.
Maybe you see the problem better if you run it for youreself
var Rnumber = new Array();
for (i = 0; i<= 100;i++)
{
Rnumber[i] = Math.ceil(Math.random()*101);
}
function unevenAndDivisible()
{
var remainder = [];
var answer = [];
for (i = 0; i<= 100; i++)
{
if (Rnumber[i]%2 !== 0)
{
remainder.push(Rnumber[i]);
for (c = 0; c <= remainder.length;c++)
{
if (remainder[c]%7 == 0)
{
answer.push(remainder[c]);
}
}
}
}
return answer;
}
answer = unevenAndDivisible();
document.write(answer);
Problem solved , Thanks everyone
You don't need to pass Rnumber to the function, as it's already available in scope:
function unevenAndDivisible()
{
var remainder = [];
for (i = 0; i<= 100; i++)
{
if (Rnumber[i]%2 !== 0) {
remainder.push(Rnumber[i]);
}
}
return remainder;
}
remainder = unevenAndDivisible();
console.log(remainder);
JS Fiddle demo.
Edited in response to question from OP (in comments to question, above):
...can someone explain what this mean: var remainder = [];
Sure, it's array-literal notation, which is equal to: var remainder = new Array();, it's just a little more concise, and I prefer to save myself the typing. I get the impression, from JS Lint, whenever I use var x = new Array(); therein that the above version is generally preferred (since it complains otherwise), but I don't know why.
Either pass Rnumber to the function unevenAndDivisible or omit it from the argument list. Since it is an argument, it has more local scope than the initial declaration of Rnumber.
Your problem is the line
function unevenAndDivisible(Rnumber)
You are passing in Rnumber in as an argument, but when you call unevenAndDivisible()
you are not passing it it.
Consequently for the body of the function Rnumber is undefined (cause you passed nothing in)
The following snippet is equivalent to what you wrote nad might explain better
function unevenAndDivisible(xyz)
{
var remainder = new Array();
for (i = 0; i<= 100; i++)
{
remainder = parseInt(xyz[i])%2;
}
return remainder;
}
then called as
unevenAndDivisible(undefined)
to fix it remove the argument from the call definition
i.e. define it as
function unevenAndDivisible()
1 - you is not defining the Rnumber value that's function argument.
2 - in loop, you're defining remainder to divised value of ranumber and is not saving in array; try:
change:
remainder = parseInt(Rnumber[i])%2;
to
remainder[i] = parseInt(Rnumber[i])%2;
var array = [],
i = 100;
while (i--) {
var random = Math.random()*i|0;
if(random % 2)
array.unshift(random);
}
alert(array);