In my project I have to do some two-deep loop procedure several (meaning a lot of) times. I'll have to do the same:
for (var i = 0; i < length; i++) {
something_here_maybe;
for (var j = 0; j < second_length; j++) {
something_else_here;
}
perhaps_other_thing_here;
}
Now I don't want to keep doing that, so I tried some:
function traverse(before, inside, after) {
for (var i = 0; i < length; i++) {
(before) ? before(i) : null;
for (var j = 0; j < second_length; j++) {
(inside) ? inside(i, j) : null;
}
(after) ? after(i) : null;
}
}
Of course, that seemed much more desirable for me, given that I thought I could do something like:
traverse(function(x) { blabla; }, function(x, y) { blabla; }, function(x) { blabla; });
Mbut ... I simply got to the point where those three functions need to interact with one another. And the variables in them are local - so they can't interact. I'd need to define those variables in traverse(), but I don't know beforehand what variables I'll need. I'd try to define another "initialize" parameter in traverse (as the first argument) which would be a function that initializes those values. But it would still be a function, and those variables would still be local to it, not taken by traverse();
Could you help me with any ideas about this approach ? Or it simply can't be done ? Any idea or advice would be much appreciated. Thank you in advance.
You could use inner functions to accomplish what you are describing, so, if the function traverse takes a parameter based on which the functions A, B and C get defined, define them by writing a function inside the traverse function which returns the three functions as it's return value, (you can return an array with the three functions in them) and then invoke the three functions in your loop.
example:
function traverse(param) {
function defineProcedures(args) {
/* local vars which have function scope(visible to the entire defineProcedures
body
*/
var funcA = function(params) { //blah };
var funcB = function(params) { //blah };
var funcC = function(params) { //blah };
return [funcA, funcB, funcC];
}
var procs = defineProcedures(param);
var firstFunc = procs[0],
secondFunc = procs[1],
thirdFunc = procs[2];
//for loops go here and invoke the functions appropriately.
}
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 );
}
}
Chrome profiling say: "Not optimized: assignment to parameter in arguments object". What can i do for optimize this code?
this.buffer.forEach(function(tilepos, ypos)
{
tilepos.forEach(function(tileinfo, xpos)
{
_self.tiles.putTile('ground', xpos, ypos, _self.ground);
});
});
It likely does not like that you are acting on tilepos within the block.
I would recommend doing it as follows if you wish to eliminate the error, but also get a bit of a performance boost:
for(var i = 0; i < this.buffer.length; i++)
{
for(var j = 0; j < this.buffer[i].length; j++)
{
_self.tiles.putTile('ground', i, j, _self.ground);
}
}
You are most likely overwriting the arguments by reassigning a function parameter or an arguments element.
function f(a) {
a = 100; // reassigns arguments[0]
};
Or
function f(a) {
arguments[0] = 100; // same thing
};
Your code looks fine to me, so I assume the problem lies in your putTile method. If you need to reassign the function parameters, create a local copy of it:
function f(a) {
var localA = a;
localA = 100;
}
If you provide the putTilemethod then I can take a look and update my answer.
I am using a getJSON method to post the data I have in a database, through a for loop and into an HTML page. But I would like to the function to call different tables in my database depending on the integer the for loop is currently on, something like this:
for (var r = 0; r < 8; r++){
$.getJSON("PHP-PAGE.php?jsoncallback=?", function(table+r) {
//function stuff here
});
}
But when I try to do this, the "table+r" is flagging a syntax error. What am I doing wrong?
You are defining a function, not calling it. Between ( and ) you have to put identifiers (variable names) not expressions.
To pass data here, you need to use variables from a wider scope than the function. Since the variable is going to change (and the function is called asynchronously) you have to use a closure to do this.
function mkCallback(table) {
var foo = "table" + table;
return function () {
// function stuff that uses foo here
// foo from the time mkCallback was called to make this function
// will still be in scope
};
}
for (var r = 0; r < 8; r++){
$.getJSON("PHP-PAGE.php?jsoncallback=?", mkCallback(table+r));
}
function(table+r) { tries to create a function with table+r as a parameter, but + is not valid in a variable name. I think you instead want something like this:
for (var r = 0; r < 8; r++){
$.getJSON("PHP-PAGE.php?jsoncallback=?",
(function(currentR){
return function() {
var someVariable=table+currentR; // No idea where table came from...
//function stuff here
}
})(r));
}
As #Quentin mentioned by the time the callback is called, r will have reached its final value, hence the interesting closure.
I think what you probably want is
for (var r = 0; r < 8; r++){ //outer loop
function(tablenum){ //closure function
tablename = table+tablenum // saved reference to "table+r"
$.getJSON("PHP-PAGE.php?jsoncallback=?", function() {
//function stuff here, using tablename as the param
});
}(r)
}
This creates a closure to maintain the value of the iterated value. You can reference tablename in the callback function, and that will refer to a value equivalent to table+r
The issues with your original example
You were putting table+r as a parameter to a function you were defining, rather than an argument to one you were calling
You were trying to get the callback to reference r. But the callback won't run until after the loop has executed, so r will be 8 for all callback functions.
If you were trying to reference "table1", "table2" then you want to have "table"+r. Otherwise I assume you're referencing a table variable outside the scope of the code you showed us.
You can directly reference the variable r in your callback. Not sure what table is - the return data from the JSON call? Try the following:
for (var r = 0; r < 8; r++){
$.getJSON("PHP-PAGE.php?jsoncallback=?", function(jsonReturnData) {
//function stuff here
alert(r);
});
}
I have a code similar to this:
$.ajax({
success: function(data) {
text = '';
for (var i = 0; i< data.length; i++) {
text = text + '' + data[i].Name + "<br />";
}
$("#SomeId").html(text);
for (var i = 0; i< data.length; i++) {
$("#Data_"+i).click(function() {
alert(data[i]);
RunFunction(data[i]);
return false;
});
}
}
});
This gets an array of some data in json format, then iterates through this array generating a link for each entry. Now I want to add a function for each link that will run a function that does something with this data. The problem is that the data seems to be unavailable after the ajax success function is called (although I thought that they behave like closures). What is the best way to use the queried json data later on? (I think setting it as a global variable would do the job, but I want to avoid that, mainly because this ajax request might be called multiple times)
Thanks.
Your problem is that the i variable is shared by the callbacks.
Therefore, all of the callbacks run on the last item.
The simplest solution is to use $.each:
$.each(data, function(i) {
$("#Data_" + i).click(function() {
alert(data[i]);
RunFunction(data[i]);
return false;
});
});
This will make a separate function call for each iteration, so there will be a separate i variable (or, in this case, parameter) for each iteration.
You can use .bind() directly and passing the data:
for (var i = 0; i< data.length; i++) {
$("#Data_"+i).bind('click', {data: data[i]}, function() {
alert(event.data.data);
RunFunction(event.data.data);
return false;
});
}
I think you made a classical mistake, trying to generate functions in a loop. The variable i will have the same value for all functions but it is not even a valid array index anymore at the end of the loop.
See also JavaScript Closures for Dummies (no offense), example 5.
SLaks answer is a good one, but he failed to explain why it wasn't working.
The problem is due to scoping. Try this out:
var logger = function(x){
console.log(x);
};
for(var j = 0; j < 10; j++){
window.setTimeout(function(){
logger(j);
}, 1000);
}
That nice little function prints out nothing but...9s! That's because the reference to j is kept by the timeout, so by the time the timeout runs, j is already set to 9.
Contrast that with:
var logger = function(x){
console.log(x);
};
for(var j = 0; j < 10; j++){
// we're wrapping our call in an anonymous function
// don't do this in production code...make the function external instead
// so you don't create 10 functions
(function(k){
window.setTimeout(function(){
logger(k);
}, 1000);
})(j);
}
This version wraps the inner call in an anonymous function that takes as an argument the index. Since the scope of k is now limited to that function, the logger works as you'd expect.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I am very puzzled about this code:
var closures = [];
function create() {
for (var i = 0; i < 5; i++) {
closures[i] = function() {
alert("i = " + i);
};
}
}
function run() {
for (var i = 0; i < 5; i++) {
closures[i]();
}
}
create();
run();
From my understanding it should print 0,1,2,3,4 (isn't this the concept of closures?).
Instead it prints 5,5,5,5,5.
I tried Rhino and Firefox.
Could someone explain this behavior to me?
Fixed Jon's answer by adding an additional anonymous function:
function create() {
for (var i = 0; i < 5; i++) {
closures[i] = (function(tmp) {
return function() {
alert("i = " + tmp);
};
})(i);
}
}
The explanation is that JavaScript's scopes are function-level, not block-level, and creating a closure just means that the enclosing scope gets added to the lexical environment of the enclosed function.
After the loop terminates, the function-level variable i has the value 5, and that's what the inner function 'sees'.
As a side note: you should beware of unnecessary function object creation, espacially in loops; it's inefficient, and if DOM objects are involved, it's easy to create circular references and therefore introduce memory leaks in Internet Explorer.
I think this might be what you want:
var closures = [];
function createClosure(i) {
closures[i] = function() {
alert("i = " + i);
};
}
function create() {
for (var i = 0; i < 5; i++) {
createClosure(i);
}
}
The solution is to have a self-executing lambda wrapping your array push. You also pass i as an argument to that lambda. The value of i inside the self-executing lambda will shadow the value of the original i and everything will work as intended:
function create() {
for (var i = 0; i < 5; i++) (function(i) {
closures[i] = function() {
alert("i = " + i);
};
})(i);
}
Another solution would be to create yet another closure which captures the correct value of i and assigns it to another variable which would "get caught" in the final lambda:
function create() {
for (var i = 0; i < 5; i++) (function() {
var x = i;
closures.push(function() {
alert("i = " + x);
});
})();
}
Yes closures are working here. Each time you loop the function you are creating grabs the i. Each function you create shares the same i. The problem you are seeing is that since they all share the same i they also share the final value of i since it is the same captured variable.
Edit: This article by Mr. Skeet explains closures in some depth and addresses this issue in particular in a way that is much more informative then I have here. However be careful as the way that Javascript and C# handle closures have some subtle differences. Skip to the section called "Comparing capture strategies: complexity vs power" for his explanation on this issue.
John Resig's Learning Advanced JavaScript explains this and more. It's an interactive presentation that explains a lot about JavaScript, and the examples are fun to read and execute.
It has a chapter about closures, and this example looks a lot like yours.
Here's the broken example:
var count = 0;
for ( var i = 0; i < 4; i++ ) {
setTimeout(function(){
assert( i == count++, "Check the value of i." );
}, i * 200);
}
And the fix:
var count = 0;
for ( var i = 0; i < 4; i++ ) (function(i){
setTimeout(function(){
assert( i == count++, "Check the value of i." );
}, i * 200);
})(i);
Just defining an inner function, or assigning it to some variable:
closures[i] = function() {...
does not create a private copy of the whole execution context. The context isn't copied until the nearest outer function is exiting (at which point those external variables could be garbage collected, so we'd better grab a copy).
This is why wrapping another function around your inner function works - the middle guy actually executes and exits, cuing the innermost function to save his own copy of the stack.
Here is what you should do to achieve your result:
<script>
var closures = [];
function create() {
for (var i = 0; i < 5; i++) {
closures[i] = function(number) {
alert("i = " + number);
};
}
}
function run() {
for (var i = 0; i < 5; i++) {
closures[i](i);
}
}
create();
run();
</script>