for (var i = 0; i < 32; i++) {
var thisId = dropId+i;
$("#p"+thisId).animate({ left:"+=32px" }, function(){
if ($("#p"+thisId).position().left == 1024) {
$("#p"+thisId).remove();
window.console.log("removed");
}
});
}
In the above code example, by the time I get around to executing animate's complete function, thisId represents the last assigned value from the for loop NOT the value that I wanted to pass in for each iteration of the loop. Is there a way to get it to access the correct thisId?
JavaScript does not have block scope. You can create a new scope by calling a function. E.g.
for (var i = 0; i < 32; i++) {
(function(thisId) {
$("#p"+thisId).animate({ left:"+=32px" }, function(){
if ($("#p"+thisId).position().left == 1024) {
$("#p"+thisId).remove();
window.console.log("removed");
}
});
}(dropId+i)); // <-- calling the function expression and passing `dropId+i`
}
Variables declarations area always hoisted to the top of the function. So even if you have the declaration inside the loop, it is actually the same as:
var i, thisId;
for(...) {
thisId = dropId + i;
//...
}
Every closure you create inside the loop references the same thisId. It's like in Highlander: "There can be only one."
You need to use a closure around the current thisId.
for (var i = 0; i < 32; i++) {
var thisId = dropId+i,
complete = (function(id) {
return function() {
if ($("#p"+id).position().left == 1024) {
$("#p"+id).remove();
window.console.log("removed");
}
}
}(thisId));
$("#p"+thisId).animate({ left:"+=32px" }, complete);
}
Just wrapping what you had in an anonymous function should work:
for (var i = 0; i < 32; i++) {
(function() {
var thisId = dropId+i;
$("#p"+thisId).animate({ left:"+=32px" }, function(){
if ($("#p"+thisId).position().left == 1024) {
$("#p"+thisId).remove();
window.console.log("removed");
}
});
})();
}
Related
I have the following script that opens urls in a list:
function openWindow(){
var x = document.getElementById('a').value.split('\n');
for (var i = 0; i < x.length; i++)
if (x[i].indexOf('.') > 0)
if (x[i].indexOf('://') < 0)
window.open('http://'+x[i]);
else
window.open(x[i]);
}
However, I would like to add a delay (let's say about 5 seconds) between opening each url. How can I do this?
I'm not familiar with functions. Usually much better with Linux and such. Your insight is highly appreciated.
A better approach is to use setTimeout() along with a self-executing anonymous function:
function openWindow() {
var i = 0;
var x = document.getElementById('a').value.split('\n');
(function() {
if(typeof x[i] !== 'undefined') {
if(x[i].indexOf('.') > 0) {
if(x[i].indexOf('://') < 0) {
window.open('http://' + x[i++]);
} else {
window.open(x[i++]);
}
}
setTimeout(arguments.callee, 1000);
}
return false;
})();
}
This will guarantee that the next call is not made before your code was executed. I used arguments.callee in this example as a function reference. Once the index no longer exists in the array, by checking if it's undefined, it simply returns false instead of setting another timout.
You can do it like this, to avoid issues caused by setTimeout being non-blocking.
What you need is to wait for the setTimeout to be executed before starting the next iteration.
var i = 0;
function openWindow(){
var x = document.getElementById('a').value.split('\n');
doLoop(x);
}
function doLoop(x)
setTimeout(function () {
if (x[i].indexOf('.') > 0){
if (x[i].indexOf('://') < 0){
window.open('http://'+x[i]);
}else{
window.open(x[i]);
}
}
i+=1;
if(i<x.length){
doLoop(x);
}
}, 5000)
}
Using a self executing function, it'd go like this :
function openWindow() {
var i = 0;
var x = document.getElementById('a').value.split('\n');
(function fn() {
if(x[i].indexOf('.') > 0) {
if(x[i].indexOf('://') < 0) {
window.open('http://' + x[i++]);
} else {
window.open(x[i++]);
}
}
i++;
if( i < x.length ){
setTimeout( fn, 3000 );
}
})();
}
create array x with all url's
var x = [url1, url2, url3, ...];
create a for loop
for(var i = 0; i<x.length; i++) {
setTimeout(function() {
window.open('http://'+x[i])}, 1000); // 1000 for 1 second
}
}
setInterval(function(){window.open('http://'+x[i]);},5000);
I'm creating an object literal and I want to use the reserved word "this". The problem I'm having is that the "this" points to the window object in an object literal. I know the this points to the current object when used in a constructor function. Is there a way to override it so that "this" points to my object literal?
main = {
run: function()
{
var elements = [];
var allElements = document.querySelectorAll("*");
for(var i = 0; i < allElements.length; i++)
{
if(allElements[i].nodeType != 3)
{
elements.push(allElements[i]);
}
}
for(var i = 0; i < elements.length; i++)
{
// Doesn't work
// this.parseElement(elements[i]);
// Works
main.parseElement(elements[i]);
}
},
parseElement: function(e)
{
// Unimportant code
}
}
(function()
{
main.run();
})();
The thing you claim works in your question doesn't work:
var main = {
run: (function()
{
var elements = [];
var allElements = document.querySelectorAll("*");
for(var i = 0; i < allElements.length; i++)
{
if(allElements[i].nodeType != 3)
{
elements.push(allElements[i]);
}
}
for(var i = 0; i < elements.length; i++)
{
// Doesn't work
// this.parseElement(elements[i]);
// Works
main.parseElement(elements[i]);
}
})(),
parseElement: function(e)
{
// Unimportant code
}
};
<div></div>
Fundamentally, you cannot refer to the object being constructed from within the object initializer. You have to create the object first, because during the processing of the initializer, while the object does exist no reference to it is available to your code yet.
From the name run, it seems like you want run to be a method, which it isn't in your code (you've edited the question now to make it one). Just remove the ()() around the function:
var main = {
run: function() {
var elements = [];
var allElements = document.querySelectorAll("*");
for (var i = 0; i < allElements.length; i++) {
if (allElements[i].nodeType != 3) {
elements.push(allElements[i]);
}
}
for (var i = 0; i < elements.length; i++) {
this.parseElement(elements[i]);
}
},
parseElement: function(e) {
console.log("Parsing " + e.tagName);
}
};
main.run();
<div></div>
Since this is set by how the function is called for normal functions, if you want run to be bound to main so that it doesn't matter how it's called, using main instead of this is the simplest way to do that in that code.
But if you don't want to use main, you could create a bound function:
var main = {
run: function() {
var elements = [];
var allElements = document.querySelectorAll("*");
for (var i = 0; i < allElements.length; i++) {
if (allElements[i].nodeType != 3) {
elements.push(allElements[i]);
}
}
for (var i = 0; i < elements.length; i++) {
this.parseElement(elements[i]);
}
},
parseElement: function(e) {
console.log("Parsing " + e.tagName);
}
};
// Bind run
main.run = main.run.bind(main);
// Use it such that `this` would have been wrong
// if we hadn't bound it:
var f = main.run;
f();
<div></div>
Just as a side note, we can use Array.prototype.filter and Array.prototype.forEach to make that code a bit more concise:
var main = {
run: function() {
var allElements = document.querySelectorAll("*");
var elements = Array.prototype.filter.call(allElements, function(e) {
return e.nodeType != 3;
});
elements.forEach(this.parseElement, this);
},
parseElement: function(e) {
console.log("Parsing " + e.tagName);
}
};
// Use it
main.run();
<div></div>
That assumes that parseElement only ever looks at the first argument it's given (since forEach will call it with three: the entry we're visiting, its index, and the object we're looping through).
in my React-native app I am trying to call another function within my listenForItems function, but keep getting the error this.populateArray is not a function. In 'this.populateArray(solutions)', this.populateArray is undefined. I do this in other classes and it's working, but for some reason it's not working here. Is there anything I'm missing?
populateArray: function(solutions) {
var completed = [];
var inProgress;
for (var i = 0; i < solutions.length; i++ ) {
if (solutions[i].completed == 0) {
inProgress = solutions[i].id;
}
else {
completed.push(solutions[i].id);
}
}
},
listenForItems: function(cluesRef) {
var solutions = [];
userSolutionsRef.orderByChild('user_id').startAt(0).endAt(0).once('value', function(snap){
var solution = snap.val();
for (var i = 0; i < solution.length; i++) {
if (solution[0].hunt_id == 0) {
solutions.push(solution[0]);
}
}
this.populateArray(solutions);
});
},
The classic this scope issue of javascript. Google will help with better understanding. In short, the word "this" inside a function refers to that function. In this example it refers the anonymous function (callback) that you use in userSolutionsRef.orderByChild. There are many ways to solve this. You can use ES6(ES2015) arrow functions in which case it becomes something like
userSolutionsRef.orderByChild('user_id').startAt(0).endAt(0).once('value', (snap) => {
var solution = snap.val();
for (var i = 0; i < solution.length; i++) {
if (solution[0].hunt_id == 0) {
solutions.push(solution[0]);
}
}
this.populateArray(solutions);
});
or es5 solution
var that = this;
userSolutionsRef.orderByChild('user_id').startAt(0).endAt(0).once('value', function(snap){
var solution = snap.val();
for (var i = 0; i < solution.length; i++) {
if (solution[0].hunt_id == 0) {
solutions.push(solution[0]);
}
}
that.populateArray(solutions);
});
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
JavaScript closures and variable scope
Assign click handlers in for loop
I have this script:
var MyClass = {
MyArray: new Array(0, 1, 2, 3, 4),
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
var cen = document.getElementById("cen_" + i); // It is an img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc1(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
}
},
MyFunc2: function(id) {
alert(id);
}
}
My problem is that, at this line :cen.onclick = function() { MyClass.MyFunc1(i); }; the argument sent to MyFunc2 is always -1. The MyFunc1 function should create four images, each one with an onclick event. When you click on each image, the MyFunc2 function should show the corresponding i value. It looks like the i value is not "saved" for each event and image element created, but only its "pointer".
Thanks!
You should be familiar with the concept of JavaScript closures to understand why this happens. If you are, then you should remember that every instance of the
function() { MyClass.MyFunc1(i); };
function closure contains i's value of -1 (since it is the final value of this variable after the entire loop finishes executing.) To avoid this, you might either use bind:
cen.onclick = (function(i) { MyClass.MyFunc1(i); }).bind(null, i);
or use an explicitly created closure with the proper i value.
It's a normal case and misunderstand of closures, see this thread and you may get some clue, the simply way to fix this problem is to wrap your for loop body with an Immediate Invoked Function Expression
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
(function(i) {
var cen = document.getElementById("cen_" + i); // An img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc2(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
}(i));
}
}
You are capturing a variable that changes inside the loop, so you always get the last value of i.
You can easily fix that by creating a closure:
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
(function(i) {
var cen = document.getElementById("cen_" + i); // An img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc2(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
})(i);
}
},
I would like to do the something along the following:
for (var i = 0; i < 10; ++i) {
createButton(x, y, function() { alert("button " + i + " pressed"); }
}
The problem with this is that I always get the final value of i because Javascript's closure is not by-value.
So how can I do this with javascript?
One solution, if you're coding for a browser that uses JavaScript 1.7 or higher, is to use the let keyword:
for(var i = 0; i < 10; ++i) {
let index = i;
createButton(x, y, function() { alert("button " + index + " pressed"); }
}
From the MDC Doc Center:
The let keyword causes the item
variable to be created with block
level scope, causing a new reference
to be created for each iteration of
the for loop. This means that a
separate variable is captured for each
closure, solving the problem caused by
the shared environment.
Check out the MDC Doc Center for the traditional approach (creating another closure).
for(var i = 0; i < 10; i++) {
(function(i) {
createButton(function() { alert("button " + i + " pressed"); });
})(i);
}
Note that JSLint doesn't like this pattern. It throws "Don't make functions within a loop.".
Live demo: http://jsfiddle.net/simevidas/ZKeXX/
Create a new scope for the closure by executing another function:
for(var i = 0; i < 10; ++i) {
createButton(x,y, function(value) { return function() { alert(...); }; }(i));
}
http://www.mennovanslooten.nl/blog/post/62
You need to put the closure into a separate function.
for(var dontUse = 0; dontUse < 10; ++dontUse) {
(function(i) {
createButton(x, y, function() { alert("button " + i + " pressed"); }
})(dontUse);
}
Thise code creates an anonymous function that takes i as a parameter for each iteration of the loop.
Since this anonymous function has a separate i parameter for each iteration, it fixes the problem.
This is equivalent to
function createIndexedButton(i) {
createButton(x, y, function() { alert("button " + i + " pressed"); }
}
for(var i = 0; i < 10; ++i) {
createIndexedButton(i);
}
for(var i = 0; i < 10; ++i) {
createButton(x, y, (function(n) {
return function() {
alert("button " + n + " pressed");
}
}(i));
}
The anonymous function on the outside is automatically invoked and creates a new closure with n in its scope, where that takes the then current value of i each time it's invoked.