I am trying to understand the javascript closure. I read a example 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;
}
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]();
}
This code will print out "item3 undefined" alert 3 times. I do understand the "3" from the item variable at line 5, but I do not understand why does it print out "undefined" from the list[i] at line 5? Isn't this also uses the closure to access the list variable? Could some one explain this?
You do have access to all those variables. The problem is your i variable in the following loop:
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
The i is passed by reference and is increased every loop. So after you've pushed the closure to the loop 3 times i's value is 4 and every callback tries to alert the 4th element of [1,2,3] (the array you provided), which is undefined.
The quickest explanation is: a closure is created around a function and not around a statement!
Try this to know what it means:
function createClosure(item, val) {
return function() {alert(item + ' ' + val)};
}
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( createClosure(item, list[i]) );
}
return result;
}
Related
I am trying to simply print out a global array list of students that I have imported from a csv file. I've done enough troubleshooting to know that the data is being imported and read fine. The common answer seems to be that you don't require a "var" declaration for a global array but this wasn't working for me either.
Here is my declaration:
//Student array list from csv import
studentList = [];
//window.studentList = [];
This is where I initialize the array:
function processData(csv){
let allLines = csv.split(/\r\n|\n/);
for(let i = 0; i < allLines.length; i++)
{
let row = allLines[i].split(",");
let col = [];
for(let j = 0; j < row.length; j++)
{
col.push(row[j]);
}
if(col == " ") break;
studentList.push(col);
}
//when I alert the array element by element the data is being read from within this function
for(let i =0; i < studentList.length; i++)
{
alert(studentList[i]);
}
}
But if I was to use a get method to return the elements I would get an 'undefined' error
function getStudent(index) {
return studentList[index];
}
for(let i = 0; i < studentList.length; i++)
{
alert(getStudent[i]);
}
EDIT: Despite that solution being right, I still have the same issue when calling from another function. For example in the following I need to return the trip departure for each student which is undefined.
function getStudentsDeparture(i)
{
trip.departure = getStudent(i);
alert(trip.departure); //prints undefined
trip.destination = "123 Smith Rd, Maddingley VIC 3340";
console.log('dest is: ' + trip.destination + ' dept is: ' +
trip.departure);
}
The issue seems to be that you try to get an index from the function getStudent[i]. Try change that row to alert(getStudent(i)); with parenthesis.
EDIT
I tested with this code and it works fine for me
studentList = [];
studentList.push('student1');
function getStudent(index) {
return studentList[index];
}
function getStudentsDeparture(i) {
var student = getStudent(i);
alert(student);
}
getStudentsDeparture(0);
The following snippet is from "Example 5" from this thread on JS and closure. Alternatively, this other thread sort of gets at what I'm curious about.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
The thread this snippet is from says the output is: "item3 undefined"x3. I understand why "item3" is printed all three times, but I don't understand why list[i] is undefined.
My Question: since list is a parameter of buildList, does that mean it's not a part of the closure of the anonymous function within result.push? If there were a variable within buildList (otherList) which was set to list (and the anonymous function used otherList[i]), would otherList[i] be in the closure? (Therefore instead of "item3 undefined"x3, the output would be "item3 3"x3).
It has three elements, so maximum valid index is 2. So it is not printing item3 undefined it is item2 undefined.
Reason :
Since you know that the function is printing item-2 string each time because, the function was bounded to the reference, not the value. (This can be avoided using Immediate invoking functions).
But the loop stops/halts, when i cross the i < array.length, So the last updated value of i is now 3 and array[1, 2, 3] at index 3 is undefined. Hence you are seeing undefined value.
Verification:
add console.log as shown below
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push( function() {
console.log(i); //Prints 3 for all 3 invocation.
alert(item + ' ' + list[i])
} );
}
Is there any way to assign a variable as the conditional operator in a for loop in Javascript?
For example, take this for loop:
for(var i = 0, j = 5; i < 10; i++, j++) {
console.log(i + ", " + j);
}
I want to be able to set the i < 10 part as a variable. I might want the for loop to run until j < 8 instead of i < 10. The way I am currently doing it is using an if statement and then using two different for loops with the exact same code except for the conditional, and I just wanted to know if a more efficient way was possible. Thanks!
Use a function in the for loop and reassign it to whatever you want.
var predicate = function(i, j) { return i < 10; }
for(var i = 0, j = 5; predicate(i, j); i++, j++) {
console.log(i + ", " + j);
if (i === 5) { // Dummy condition
predicate = function(i, j) { return j < 8; };
}
}
The advantage is that you can completely change the logic if you need to.
You can replace the 10 with a variable. You cannot replace the operator (<) with a variable.
You can replace the entire expression with a function though, and that function can be stored in a variable.
var data = ["a", "b", "c"];
function loop(callback) {
for (var i = 0; callback(i); i++) {
console.log(data[i]);
}
}
loop(function (i) {
return (i < 10);
});
loop(function (i) {
return (i < 8);
});
Sure, did you try just sticking variables in there :
var total = true==true ? 8 : 10,
i = 0,
j = 5,
what = true==false ? i : j;
for(; what < total; i++, j++) {
console.log(i + ", " + j);
}
I guess you can create a random function for this?
function countdown = (function(){
var i =0, j = 5;
return function(){
++i; ++j;
return i < 10; // or j < 8
}
})();
while(countdown()) {
console.log('counting');
}
The only problem with that is that you cannot access i and j in the scope of your loop.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Access outside variable in loop from Javascript closure [duplicate]
(5 answers)
Closed 9 years ago.
I try to dynamically generate <table> with jQuery and I want to set click handlers to each cell, so when cell clicked the popup will appear with current index of cell. How I can access to CURRENT i and j variables in loop?
for(var i = 0; i < 5; i++) {
var tr = $('<tr></tr>');
for (var j = 0; j < 5; j++) {
var td = $('<td></td>');
td.click(function() {
alert(i + ' ' + j); // here I want to access to CURRENT i, j variables
})
td.appendTo(tr);
}
}
The other answers mostly explain how to fix the closure problem, although such methods are not particularly memory efficient since they end up creating a new closure for each possible combination of i and j.
However, since you're using jQuery you have a number of other options available to you:
Pass data parameters to the .on call
td.on('click', { i: i, j: j }, function(event) {
var i = event.data.i;
var j = event.data.j;
alert(i + ' ' + j);
});
The i and j values as you can see above will be available in event.data
Use $.each to iterate instead of a for
$.each(Array(5), function(i) {
// i is already bound here because it's a function parameter
$.each(Array(5), function(j) {
// and j available here
td.on('click', ...);
});
});
Use event delegation instead of a per-element handler
$('#myTable').on('click', 'td', function() {
// use jQuery methods to determine the index, e.g.
var j = this.cellIndex
var i = this.parentNode.rowIndex
...
});
which is more efficient than binding a separate handler to each <td> individually.
and more generically, use .data() to store per-cell information
You can store data values directly on elements, which would work very well if you wanted to retrieve values other than the cell and row indexes:
td = $('<td>').data({i: i, j: j});
and then extract these row and column values directly from the clicked element's .data:
for (var i = 0; i < 5; i++) {
var tr = $('<tr></tr>');
for (var j = 0; j < 5; j++) {
$('<td></td>').data({i: i, j: j}).appendTo(tr);
td.appendTo(tr);
}
}
$('#myTable').on('click', 'td', function() {
var data = $(this).data();
var i = data.i;
var j = data.j;
alert(i + ' ' + j);
});
Introducing them into a new scope will capture their current value:
(function(i, j) {
td.click(function() {
alert(i + ' ' + j); // here I want to access to CURRENT i, j variables
});
})(i, j);
You can create a new scope for the 'current' value of (i, j) by executing a function inside the loop like this
td.click((function (i, j) {
return function (event) {
console.log(i, j);
}
})(i, j));
You could make this a little more succinct by using Function.prototype.bind
td.click(function(i, j, event){
console.log(i, j);
}.bind(td, i, j));
Note This .bind solution requires ECMAScript 5, so if you want to support older browsers, please look into es5-shim
See this demo
Other option can be storing data within the elements.
for(var i = 0; i < 5; i++)
{
var tr = $('<tr></tr>');
for (var j = 0; j < 5; j++)
{
var td = $('<td></td>');
this.data("position",{row:i, column:j});
td.click(function()
{
var position = this.data("position");
alert(position.row + ' ' + position.column); // here I want to access to CURRENT i, j variables
})
td.appendTo(tr);
}
}
I would store the indexes as attributes and add a single click handler to the table :
var tableHtml = '';
for(var i=0; i<5; i++) {
tableHtml += '<tr>';
for (var j=0; j<5; j++) {
tableHtml += '<td data-i="' + i + '" data-j="' + j + '"></td>';
}
tableHtml += '</tr>';
}
$('table').append(tableHtml).on('click', 'td', function(ev) {
var i = this.getAttribute('data-i'),
j = this.getAttribute('data-j');
});
function Qnt_Box(e) {
var app = UiApp.getActiveApplication();
var Vpanel = app.getElementById('Vpanel');
app.getElementById('button2').setVisible(false);
var qnt = e.parameter.Quant;
var grid2 = app.createGrid(qnt, 2);
for (var i = 0; i < qnt ; i++) {
grid2.setWidget(i, 0, app.createLabel(i + 1 + ' :'));
grid2.setWidget(i, 1, app.createTextBox().setName('x' + i)); HERE!!!
}
Vpanel.add(grid2);
return app;
}
How do I load the variables, which were named in a looping, looping one another?
My Idea was:
function example() {
for(var i =0; i < qnt+1; i++)
var aux = e.parameter.'x' + i;
}
but not work XS
Thank you!!
If the name is in a property of e.parameter, you can use Bill's suggestion
e.parameter['x' + i];
If it's a global variable, you can access it through the window object
window['x' + i]
I don't completely follow your code, but if you are trying to access a property using a variable name, you can use array notation.
function example() {
for(var i =0; i < qnt+1; i++)
var aux = e.parameter['x' + i];
}