I am having problems accessing nodes[i] from the callback function inside
chrome.bookmarks.create. Any idea guys ? I am thinking this is because of closure. Any way to make it work ?
function copyBookmarks(nodes,folderId){
for(i=0;i<nodes.length;i++){
var properties={
parentId:folderId,
index:nodes[i].index,
title:nodes[i].title,
url:nodes[i].url
};
chrome.bookmarks.create(properties,function(newNode){
console.log(nodes[i]);//this doesnt work
});
}
}
It is accessing nodes just fine, but the problem is that i will be the value after the loop completes. The usual solution is to make a copy of i in each iteration through a self-executing function:
for (var i = 0; i < nodes.length; i++) {
// Other code...
// Self executing function to copy i as a local argument
(function (i) {
chrome.bookmarks.create(properties, function (newNode) {
console.log(nodes[i]);
});
})(i);
}
Related
I have been working through this article on closures: Understand Javascript Closures with Ease
The final example deals with a closure inside a for loop.
I understand why an IIFE is used to capture the current value of 'i' as 'j'. What I don't understand about this example is why there is a 2nd, inner IIFE wrapped around the return statement. (My comment is in caps in the code below).
The code seems to work just the same without the inner IIFE. See CodePen here.
Is this inner function required for some reason, or is this just an oversight by the author?
function celebrityIDCreator (theCelebrities) {
var i;
var uniqueID = 100;
for (i = 0; i < theCelebrities.length; i++) {
theCelebrities[i]["id"] = function (j) { // the j parametric variable is the i passed in on invocation of this IIFE
return function () { //<--WHY DOES THIS INNER FUNCTION NEED TO BE HERE?
return uniqueID + j; // each iteration of the for loop passes the current value of i into this IIFE and it saves the correct value to the array
} () // BY adding () at the end of this function, we are executing it immediately and returning just the value of uniqueID + j, instead of returning a function.
} (i); // immediately invoke the function passing the i variable as a parameter
}
return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0},{name:"Willis", id:0}];
var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
var stalloneID = createIdForActionCelebs [0];
console.log(stalloneID.id); // 100
var cruiseID = createIdForActionCelebs [1];
console.log(cruiseID.id); // 101
var willisID = createIdForActionCelebs[2];
console.log(willisID.id); //102
The inner function, as you observed, has no practical effect. It doesn't make sense to use it.
It appears to be a holdover from the previous example in the article where a function was returned instead of a number.
I seem to be unable to pass parameter to an anonymous function as the argument of a setTimeOut call. Here is the code
http://jsfiddle.net/5xg5d6pp/
var arr = ["Just a test","I miss you so much darling #$%&%#;..\]]/"];
console.log(arr);
for(var c=0; c < arr.length; c++){
console.log(arr[c]);
//wait 1 sec for next loop
setTimeout(function(arr[c]) {
do_magic(arr[c]);
}, 1000);
}
function do_magic (passed_var){
console.log(passed_var);
}
When you do this setTimeout(function(arr[c]) {
You are defining a new function and saying that I want this function to accept a parameter called 'arr[c]', you aren't saying that you want to pass arr[c] to it and because you can't have any special characters in the name of a parameter you get an error. What you should do is define a function outside of the loop to avoid the loop closure issue and pass the parameter to that letting that function create the setTimeout for you. Please see JavaScript closure inside loops – simple practical example for more information about closures. Also read this to learn more about javascript functions: http://javascript.info/tutorial/functions-declarations-and-expressions
This is the correct code below:
var arr = ["Just a test","I miss you so much darling #$%&%#;..\]]/"];
console.log(arr);
for(var c=0; c < arr.length; c++){
console.log(arr[c]);
setTimeoutFactory(arr[c]);
}
function do_magic (passed_var){
console.log(passed_var);
}
function setTimeoutFactory(text) {
setTimeout(function() {
do_magic(text);
}, 1000);
}
Can anyone explain why document.write part always outputs 10?
function creatFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
}
}
return result;
}
var funcs = creatFunctions();
for (var i = 0; i < funcs.length; i++) {
document.write(funcs[i]() + "<br />");
}
I think the answer to this question is more thorough than the duplicate question, therefore worth keeping this post.
Sure.
In your for loop, you reference i. What you expect to be happening is that each closure gets a snapshot of i at the time the function is created, therefore in the first function it would return 0, then 1, etc.
What's really happening is that each closure is getting a reference to the external variable i, which keeps updating as you update i in the for loop.
So, the first time through the loop, you get a function that returns i, which at this point is 0. The next time you get two functions which return i, which is now 1, etc.
At the end of the loop, i==10, and each function returns i, so they all return 10.
UPDATE TO ADDRESS QUESTION IN COMMENT:
It's a little confusing since you use i in two different contexts. I'll make a very slight change to your code to help illustrate what's going on:
function creatFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i;
}
}
return result;
}
var funcs = creatFunctions();
// NOTE: I changed this `i` to `unrelated_variable`
for (var unrelated_variable = 0; unrelated_variable < funcs.length; unrelated_variable++) {
document.write(funcs[unrelated_variable]() + "<br />");
}
... the functions that you create in your creatFunctions() function all return i. Specifically, they return the i that you create in the for loop.
The other variable, which I've renamed to unrelated_variable, has no impact on the value returned from your closure.
result[i] = function () {
return i;
}
... is not the same thing as result[2] = 2. It means result[2] = the_current_value_of_i
Because you reference i as a variable, which, when the function executes, has the value 10. Instead, you could create a copy of i by wrapping in in a function:
(function (i) {
result[i] = function () {
return i;
}
}(i));
The i that you are returning inside the inner function - is the i that was declared inside the for(var i=0...) therefor - it is the same i for all of the functions in result
and by the time you are calling the functions its value is 10 (after the loop ends)
to accomplish what you wanted you should declare another variable inside the scope of the anonymous function
Because reference to "i" is boxed (enclosed) in your closure that you defined.
result[i] = function () {
return i;
}
And it just keeps reference to the "i" var which keeps changing with each iteration.
UPDATE
This is related to the this keyword function scope which defines variable context.
Hence, in JavaScript, there's no such thing as block scope (for, if else, while, ...).
With that in mind, if you assign var funcs to a function definitioncall, the function body (this) will be bound to the global scope var i inside that function will reach the end of the for loop and stays there in memory. In this case i is 10.
Unfortunately for you, since function scope puts every variable to the top, var i will be global as well.
Although I was completely wrong at first, I stand corrected and took the liberty to create the output of your script in this demo.
I've run into an issue where my app lives in an iframe and it's being called from an external domain. IE9 won't fire the load event when the iframe loads properly so I think I'm stuck using setTimeout to poll the page.
Anyway, I want to see what duration is generally needed for my setTimeout to complete, so I wanted to be able to log the delay the setTimeout fires from my callback, but I'm not sure how to pass that context into it so I can log it.
App.readyIE9 = function() {
var timings = [1,250,500,750,1000,1500,2000,3000];
for(var i = 0; i < timings.length; i++) {
var func = function() {
if(App.ready_loaded) return;
console.log(timings[i]);
App.readyCallBack();
};
setTimeout(func,timings[i]);
}
};
I keep getting LOG: undefined in IE9's console.
What's the proper method to accomplish this?
Thanks
This is happening because you are not closing around the value of i in your func. When the loop is done, i is 8 (timings.length), which doesn't exist in the array.
You need to do something like this:
App.readyIE9 = function() {
var timings = [1,250,500,750,1000,1500,2000,3000];
for(var i = 0; i < timings.length; i++) {
var func = function(x) {
return function(){
if(App.ready_loaded) return;
console.log(timings[x]);
App.readyCallBack();
};
};
setTimeout(func(i),timings[i]);
}
};
When your function gets called by setTimeout sometime in the future, the value of i has already been incremented to the end of it's range by the for loop so console.log(timings[i]); reports undefined.
To use i in that function, you need to capture it in a function closure. There are several ways to do that. I would suggest using a self-executing function to capture the value of i like this:
App.readyIE9 = function() {
var timings = [1,250,500,750,1000,1500,2000,3000];
for(var i = 0; i < timings.length; i++) {
(function(index) {
setTimeout(function() {
if(App.ready_loaded) return;
console.log(timings[index]);
App.readyCallBack();
}, timings[index]);
})(i);
}
};
As a bit of explanation for who this works: i is passed to the self-executing function as the first argument to that function. That first argument is named index and gets frozen with each invocation of the self-executing function so the for loop won't cause it to change before the setTimeout callback is executed. So, referencing index inside of the self-executing function will get the correct value of the array index for each setTimeout callback.
This is a usual problem when you work with setTimeout or setInterval callbacks. You should pass the i value to the function:
var timings = [1, 250, 500, 750, 1000, 1500, 2000, 3000],
func = function(i) {
return function() {
console.log(timings[i]);
};
};
for (var i = 0, len = timings.length; i < len; i++) {
setTimeout(func(i), timings[i]);
}
DEMO: http://jsfiddle.net/r56wu8es/
I've seen programmers assign events listeners inside loops, using the counter. I believe this is the syntax:
for(var i=0; i < someArray.length; i++){
someArray[i].onclick = (function(i){/* Some code using i */})(i);
}
Could someone please explain the logic behind this, and this weird syntax, I've never seen this:
(function(i))(i);
Many thanks for your time and patience.
The (function(i))(i) syntax creates an anonymous function and then immediately executes it.
Usually you'll do this to create a new function every time through the loop, that has its own copy of the variable instead of every event handler sharing the same variable.
So for example:
for(int i = 0; i < 10; i++)
buttons[i].click = function() { doFoo(i); };
Often catches people out, because no matter what button you click on, doFoo(10) is called.
Whereas:
for(int i = 0; i < 10; i++)
buttons[i].click = (function(i){ return function() { doFoo(i); };)(i);
Creates a new instance of the inner function (with its own value of i) for each iteration, and works as expected.
This is done because JavaScript only has function scope, not block scope. Hence, every variable you declare in a loop is in the function's scope and every closure you create has access to the very same variable.
So the only way to create a new scope is to call a function and that is what
(function(i){/* Some code using i */}(i))
is doing.
Note that your example misses an important part: The immediate function has to return another function which will be the click handler:
someArray[i].onclick = (function(i){
return function() {
/* Some code using i */
}
}(i));
The immediate function is nothing special. It is somehow inlining function definition and function call. You can replace it by a normal function call:
function getClickHandler(i) {
return function() {
/* Some code using i */
}
}
for(var i=0; i < someArray.length; i++){
someArray[i].onclick = getClickHandler(i);
}