I've declared the variable elem in both of the loops below; however, when the anonymous function in the first loop gets called (after the 400ms fadeOut effect is finished) the elem seems to refer to the value of elem which was assigned in the second loop. In other words, the code works correctly if you rename elem in the second loop to any other variable name.
Is there a way to make a closure around the anonymous function so that the value of elem is not changed in the context of the anonymous function?
for (var i = 0; i < outs.length; i++) {
var elem = this.elementAtPoint(outs[i]);
$(elem).fadeOut(400, function () {
$(elem).removeClass("white black queen"); //UPDATE
$(elem).show();
});
//$(elem).css("background", "red");
}
for (var i = 0; i < ins.length; i++) {
var elem = this.elementAtPoint(ins[i]);
var piece = this.board.pieceAt(ins[i]);
$(elem).hide();
$(elem).addClass(this.classForPiece(piece));
$(elem).fadeIn(400);
}
You could use a anonymous self executing function
for (var i = 0; i < outs.length; i++) {
(function(elem){
$(elem).fadeOut(400, function () {
$(elem).removeClass("white black queen"); //UPDATE
$(elem).show();
});
//$(elem).css("background", "red");
})(this.elementAtPoint(outs[i]));
}
in javascript, variable scope is decided by function, so you can wrap the two for loops in two function. a wrapper function example:
(function(){
//put you for loop here
})();
I would avoid creating lots of closures inside loops, they would be inefficient as they create copies of the current scope. Instead I'd simply create a couple of extra functions then call them inside your loops:
var fadeInPiece = function(elem, piece){
$(elem).hide();
$(elem).addClass(this.classForPiece(piece));
$(elem).fadeIn(400);
}
var fadeOutElem = function(elem){
$(elem).fadeOut(400, function () {
$(elem).removeClass("white black queen"); //UPDATE
$(elem).show();
});
}
for (var i = 0; i < outs.length; i++)
fadeOutElement(this.elementAtPoint(outs[i]));
for (var i = 0; i < ins.length; i++)
fadeInPiece(this.elementAtPoint(ins[i]), this.board.pieceAt(ins[i]));
Related
I don't have much JavaScript experience. My question is this:
When I'm writing a JavaScript library, and many of the functions I'm writing functions are meant to call each other and users can call the functions I'm defining on each other in ways I might not have predicted but are valid, how do I keep the iterators in functions that have iterating loops straight?
Do I have to come up with new names for each iterator in a for loop every time I do a for-loop just to be safe that I haven't accidentally used the same variable in two functions where one function might nest inside the other in a situation I haven't predicted or thought of?
These are just a couple examples of functions that have iteration in them. Everything I'm writing is for working with interacting with Qualtrics surveys (shown in gif examples below).
function watchSet(set, mathFunction) {
var setSize = set.length;
for (var i=0; i < setSize; i++) {
set[i].down().observe("keyup", mathFunction );
}
}
function mathSum(set, output) {
var setTotal = 0;
for (var j=0; j < (set.length); j++) {
var setInputValue = parseInt(set[j].down().value, 10);
if (isNaN(setInputValue)) { setInputValue = 0; }
setTotal = setTotal + setInputValue;
}
output.value = setTotal;
}
function validateError(array, color) {
if (color === undefined) {
color = "pink";
}
color = color.concat(";");
for (var k=0; k < array.length; k++) {
array[k].down().setAttribute("style", "background-color: ".concat(color));
}
$('NextButton') && $('NextButton').hide();
}
function cellRange(startCell, endCell) {
var r1 = /^[A-Z]/;
var r2 = /[0-9]{1,3}$/;
var startCellColumn = r1.exec(startCell)[0].charCodeAt(0) - 61;
var endCellColumn = r1.exec(endCell)[0].charCodeAt(0) - 61;
var startCellRow = parseInt(r2.exec(startCell)[0], 10);
var endCellRow = parseInt(r2.exec(endCell)[0], 10);
var tempRange = [];
for (var q=startCellColumn; q<=endCellColumn; q++) {
for (var r=startCellRow; r<=endCellRow; r++) {
tempRange.push(q);
tempRange.push(r);
}
}
var outputRange = [];
for (var s=0; s < tempRange.length; s+=2) {
outputRange.push(cell(String.fromCharCode(tempRange[s]+61).concat(tempRange[s+1])));
}
return outputRange;
}
Gif Examples:
setting equivalency-validation
summing a couple cells
No, you don't need unique variable names in different functions.
Variables declared with var are local to the function scope in which they are declared in. They will not and do not conflict with anything outside that scope. So, your three functions watchSet(), mathSum() and validateError() can all use var i just fine and will not conflict with each other or with any third party code outside of those functions. Local variables like this are created uniquely each time the function is run and can be referred to only from within that function.
If you did not use var to explicitly declare your loop variables, then Javascript would "implicitly" create global variables by that name and then, yes, your different functions could collide if one function doing this called another so thus they were both trying to use the same global at the same time. But, as long as your variables are declared with var and your code is in a function (thus not running at the global scope), this will not happen.
You can also run your code in strict mode (highly recommended) because then an accidential implicit global is an immediate error and the interpreter will immediately show you where the problem is.
Or use .forEach()
You can also use .forEach() on arrays and not have to create your own iteration index at all.
function watchSet(set, mathFunction) {
set.forEach(function(item) {
item.down().observe("keyup", mathFunction );
});
}
Or, use let in an ES6 environment
In an ES6 environment, you can use let instead of var and the variable will be scoped to only the for loop too.
function watchSet(set, mathFunction) {
var setSize = set.length;
// when declaring with let in a for loop, the variable is scoped to
// only inside the for loop
for (let i=0; i < setSize; i++) {
set[i].down().observe("keyup", mathFunction );
}
// with let in a for declaration, even another use in the same function
// does not conflict
// this is a completely different variable than the one above
for (let i=0; i < setSize; i++) {
set[i].up().observe("keyup", mathFunction );
}
}
I have a loop where I define a closure for an event:
for (var i = 0; i < 3; i++) {
obj.onload = function(e) {
me.myFunction(e, i);
};
}
I need to pass that counter variable to the function that is called when the load event is triggered. I always get the same value being passed.
How can I pass the right counter value to the function?
The usual way to solve this is with a builder function, so that the handler closes over something that doesn't change:
function buildHandler(index) {
return function(e) {
me.myFunction(e, index);
};
}
for (var i = 0; i < 3; i++) {
obj.onload = buildHandler(i);
}
You can do that with an immediately-invoked function expression (IIFE), but in theory it creates and throws away a function on every loop (I expect most JavaScript engines these days optimize that) and I, at least, think it's a lot harder to read:
for (var i = 0; i < 3; i++) {
obj.onload = (function(index) {
return function(e) {
me.myFunction(e, index);
};
})(i);
}
There's an ES5 feature, Function#bind, that you can use if you want to use me.myFunction directly, but you'd have to change the order in which you were expecting the arguments (index first, then the event):
for (var i = 0; i < 3; i++) {
obj.onload = me.myFunction.bind(me, i);
}
bind creates a function that, when called, calls the original function with this set to the first argument (so, me above), then any further arguments you give it, and then any arguments that were used when calling the function. (That's why the order myFunction was expecting them had to change to index, e rather than e, i.)
On older browsers, you need a shim for Function#bind.
I'm adding an event listener to some elements I'm looping through and need a closure in order to preserve the index in the event function.
<button>solution 1</button>
<button>solution 2</button>
<script>
var buttons = document.getElementsByTagName('button');
for (var i = 0; i < 3; i++) {
var log = (function closure(number) {
return function () {
console.log(number);
};
})(i);
buttons[0].addEventListener("click", log);
}
for (var i = 0, len = 3; i < len; i++) {
(function (i) {
var log = function () {
console.log(i);
};
buttons[1].addEventListener("click", log);
})(i);
}
</script>
http://jsfiddle.net/paptd/11/
Both these solutions output 0, 1, 2 correctly (try 'wrong' to see what happens without a closure) but I'm trying to understand which one I should use and why.
Which way is the correct way of doing it?
The first one works because you are defining a closure, returning a function from it, then assigning that function to a listener.
The second one seems more proper, since the closure encompasses the entire loop content, making it more obvious that the value of i is to be "locked" there.
You shouldn't use any of these--you're creating n identical functions inside of your loop. You should refactor your code into a named function that returns the event handler:
var buttons = document.getElementsByTagName('button');
function createHandler(number) {
return function () {
console.log(number);
};
}
for (var i = 0; i < 3; i++) {
buttons[0].addEventListener("click", createHandler(i));
}
Example: http://jsfiddle.net/paptd/12/
I have the following code that adds an onmouseover event to a bullet onload
for (var i = 0; i <= 3; i++) {
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
}
This is the function that it is calling. It is supposed to add a css class to the menu item as the mouse goes over it.
function addBarOnHover(node) {
document.getElementById('menu').getElementsByTagName('li')[node].className = "current_page_item"; }
When the function is called, I keep getting the error:
"document.getElementById("menu").getElementsByTagName("li")[node] is
undefined"
The thing that is stumping me is I added an alert(node) statement to the addBarOnHover function to see what the value of the parameter was. The alert said the value of the parameter being passed was 4. I'm not sure how this could happen with the loop I have set up.
Any help would be much appreciated.
This is a common problem when you close over an iteration variable. Wrap the for body in an extra method to capture the value of the iteration variable:
for (var i = 0; i <= 3; i++) {
(function(i){ //here
document.getElementById('menu').getElementsByTagName('li')[i].onmouseover = function () { addBarOnHover(i); };
})(i); //here
}
an anonymous function is created each time the loop is entered, and it is passed the current value of the iteration variable. i inside the anonymous function refers to the argument of this function, rather than the i in the outer scope.
You could also rename the inner variable for clarity:
for(var i=0; i<=3; i++){
(function(ii){
//use ii as i
})(i)
}
Without capturing the iteration variable, the value of i when it is finally used in the anonymous handler has been already changed to 4. There's only one i in the outer scope, shared between all instances of the handler. If you capture the value by an anonymous function, then the argument to that function is used instead.
i is being passed as a reference (not by value), so once the onmouseover callback is called, the value of i has already become 4.
You'll have to create your callback function using another function:
var menu = document.getElementById('menu');
var items = menu.getElementsByTagName('li');
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = (function(i) {
return function() {
addBarOnHover(i);
};
})(i);
}
You could make it a little more readable by making a helper function:
var createCallback = function(i) {
return function() {
addBarOnHover(i);
};
};
for (var i = 0; i <= 3; i++) {
items[i].onmouseover = createCallback(i);
}
This should be easy but my head seems to be frazzled just now.
The result I'm aiming for is 0,1,2,0,1,2
My code so far.... here is a fiddle to play with.
function delayedLoad(page){
console.log(page);
};
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
setTimeout(function(){
delayedLoad(i);
},3000*i);
}
};
$(document).ready(function(){
// Load the content for the first time.
loadContent();
});
This is a very common issue people hit.
The trouble is that the function you're passing to setTimeout in each iteration is referencing the same i variable.
JavaScript does not have block scope, only function scope. So to create a new scope that will retain the i value you want, you need to invoke a function inside the loop, passing it i.
Example: http://jsfiddle.net/GNwhR/3/
function set_up_timeout( j ) {
setTimeout(function(){
delayedLoad( j );
},3000*j);
}
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
set_up_timeout( i );
}
};
This places your setTimeout call in another function which is invoked and passed i in each iteration. This creates a new variable scope where j in the function references the proper value.
Another approach is to have the invoked function return a function:
Example: http://jsfiddle.net/GNwhR/4/
function set_up_callback( j ) {
return function(){
delayedLoad( j );
};
}
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
setTimeout( set_up_callback( i ), 3000*i );
}
};
This one calls set_up_callback, which returns a function that references j.
Finally, another common approach is to use an IIFE (immediately invoked function expression) to eliminate the named function. This is less clear IMO:
Example: http://jsfiddle.net/GNwhR/5/
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
setTimeout((function( j ){
return function() {
delayedLoad( j );
};
})( i ),3000*i);
}
};
This is basically the same as the previous example in that it returns a function. The main difference is that the function that returns the function is created and invoked inside the loop itself.
All of these illustrate the same concept, that you need to scope a variable if you wish to asynchronously reference it later and have it retain its expected value.
patrick's explanation is pretty well right on. Here's a bit of code to help you get around it.
function delayedLoad(page){
console.log(page);
};
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log('in loop', i);
setTimeout($.proxy(function(){
delayedLoad(this.page);
}, {page: i}),3000*i);
}
};
$(document).ready(function(){
// Load the content for the first time.
loadContent();
});
jQuery's proxy just resets what 'this' is pointing to in a function.. so, you can replace passing in a number to just resetting the scope :) (if that makes any sense.. long day)