3

This solution works, but I don't understand what the second return function() does?

for (var i = 0; i < photos.length; i ++) {
    img.onclick = (function(photo) {
        return function() {
            hotLink(photo); //window.location = '/pics/user/' + photo.user_id;  
        };  
    })(photos[i]);

Also, why do I have to include the (photos[i]); at the end?

Before, I had this, and the onclick would always link to the last photo[i].

  for (var i = 0; i < photos.length; i ++) {
      img.onclick = function() {
          window.location = 'pics/user/' + photo.user_id
      };
  }


   
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
dngoo
  • 499
  • 1
  • 5
  • 16

3 Answers3

1

Because a function invocation is the only way to create a new variable scope in JavaScript.

So you pass photos[i] to that function, and it becomes local to the scope of that invocation.

Then you also create the handler function in that same scope, so the handler is referencing that specific photo.

So in the end, if the loop iterates 10 times, you're invoking 10 functions, creating 10 new separate variable scopes, which reference each individual photo and create and return the handler from each separate scope.


These things are sometimes clearer if you don't inline the function like that.

for (var i = 0; i < photos.length; i ++) {
    img.onclick = createHandler(photos[i]); // pass individual photos to createHandler
}

function createHandler(photo) {
       // In here, the individual photo is referenced

       // And we create (and return) a function that works with the given photo
    return function() {
        hotLink(photo); //window.location = '/pics/user/' + photo.user_id;
    };
}

So if the loop runs for 10 iterations, we invoke createHandler() 10 times, each time passing an individual photo.

Because each function invocation creates a variable scope, and because we create the event handler inside each scope, what we end up with is all 10 functions being created in 10 variable scopes, each of which reference whatever photo was passed.


Without the per-iteration function invocation, all the handler functions are created in the same variable scope, which means they all share the same variables, which are likely being overwritten in each loop iteration.

  • 'function invocation' as in the 'return function' ? I'm not understanding the purpose of 'return function()' btw, everything is working, I simply want to understand why 'return function' – dngoo Jun 01 '12 at 03:42
  • @dngoo: There are two functions. The one that is created and invoked immediately, and the one that is returned from that invocation. Each time the outer one is invoked, it creates a new variable scope. That scope references the individual `photo` that was passed to it. That scope also creates the handler function, which references that `photo`. Then the handler is returned, and assigned to the element. –  Jun 01 '12 at 03:43
  • Thanks @am not i am! Your answer was extremely helpful. People on stack over flow are so nice/helpful.... – dngoo Jun 01 '12 at 03:56
  • Just wondering -- why do I have to include (photos[i]); after all of that? – dngoo Jun 01 '12 at 03:57
  • dngoo: you are calling the function with photos[i] as the argument. – Mark Reed Jun 01 '12 at 03:58
  • yes, you do. Look at my answer. The (....)() is declaring a new function inside the first set of parentheses; the second set of parentheses is the function call. `(function(foo) {doStuff(foo);})(bar)` declares a function and then passes `bar` to it. Within the function, it calls that value `foo` and passes it to `doStuff`. – Mark Reed Jun 01 '12 at 04:00
  • 1
    @dngoo: It shouldn't work if you delete it. You need to pass it into the function. They key thing to understand in JavaScript is that functions are objects, and JavaScript functions create "closures". This means you can pass functions around like any other objects, and the variables the function referenced at creation will always be referenced. They "close over" their original variable scope, and carry it with them for the life of the function. –  Jun 01 '12 at 04:00
  • 1
    Try this: `(function(x){alert(x);})("hello, world!")` It will display "hello, world!". If you put something else inside the parentheses, it will display that. Whatever you put in the second set of parentheses becomes the value of x inside the first set of parentheses. `(function(a,b,c){...})(x,y,z);` is the same as `function myfunc(a,b,c) {...}; myfunc(x,y,z);` – Mark Reed Jun 01 '12 at 04:01
1

When you do this (assuming there's a photo = photos[i] there that you left out in your question):

img.onclick = function() { window.location = 'pics/user/' + photo.user_id };

The variable photo inside the function refers to the same variable as photo outside the function. It's not a snapshot that gets the current value of the variable at the time you define the function; it's just a reference to the same variable. The surrounding loop changes the value of that variable on every iteration, but it doesn't create a new variable each time; it's reusing the same one. So all the functions you generate reference that exact same variable - the one and only photo.

By the time anyone actually clicks on the image and calls the function, the loop has long since terminated, and photo is gone from the main program's scope, but it's still out there in memory because all those functions still have references to it. And they will find it still pointing to the last item in the list, because that was the last thing assigned to it.

So you need to give each onclick function its very own variable that won't change once the function is created. The way to do that in Javascript, since it doesn't have block scope, is to call a function and pass the value in as a parameter. Function parameters and variables declared inside a function (as opposed to photo in the non-working example above, which is used inside the function but declared outside it) are created fresh on every function invocation. When photo is declared as a function parameter, each onclick gets its very own copy that nothing else can modify, so it still has the right value when someone finally clicks the image.

It might be clearer if it used a static function-generator function; there's really no reason to do the inline declare-and-call thing. You could declare this once, outside the loop:

function makeOnclick(somePhoto) {
    return function() { hotlink(somePhoto); }
}

And then the loop body could do this:

img.onclick = makeOnclick(photo)

You're calling makeOnclick and passing it photo as a parameter. The makeOnclick function is declared far away, where it couldn't use photo directly even if you wanted it to; it can't see that variable at all. Instead, all it has is its local parameter somePhoto - which is created as a brand new variable every time you call makeOnclick. It's initialized with the value of photo at the point of the call, but it's just a copy, so when photo changes on the next loop iteration, that particular instance of somePhoto will stay the same. When the next iteration calls makeOnclick, it will create a new instance of somePhoto initialized to the new value of photo, and so on. So even though the inner function that makeOnClick is returning is inheriting the somePhoto var, that var was just created especially for that instance of makeOnClick; every one of those returned functions gets its own private somePhoto.

Your working code above is doing exactly the same thing in a slightly different way. Instead of declaring the makeOnclick function once, outside the loop, and calling it a bunch of times, it's redeclaring it every time through the loop as an anonymous function which it then calls immediately. This code:

img.onclick = (function(x) { blah(x); })(photo);

is the same as this:

function foo(x) { blah(x); }
img.onclick = foo(photo);

without having to give the function a name. In JavaScript in general, this:

(function (x,y,z) { doSomething(x,y,z); })(a,b,c);

is the same as this:

function callDoSomething(x,y,z) { doSomething(x,y,z); }
callDoSomething(a,b,c);

except the function has no name and is not saved anywhere; it goes away right after it's called.

So declaring the onclick-generator function every time through the loop and calling it immediately all at once is nice and concise, but not terribly efficient.

Mark Reed
  • 91,912
  • 16
  • 138
  • 175
  • Hey Mark-- thanks for your response; I understood this part. @am not i am is on a closer track to answering my lack of understanding of why the first one works – dngoo Jun 01 '12 at 03:42
  • Thanks Mark for your help. Your guys' answers are definitely helping me wrap my had around this. I'm coming from a non-tech background so it takes me more time to understand the CS stuff (for now :)). – dngoo Jun 01 '12 at 03:55
1

The returned function is a closure. When you're looping through like that i is updating on each loop until the end of the loop where you're stuck with the last image. Adding the self executing function and passing photo[i] in it will permanently enclose the current value within the returned function as photo.

Here is more information on closures: How do JavaScript closures work?

And here for more information on your current issue: JavaScript closure inside loops – simple practical example

Community
  • 1
  • 1
subhaze
  • 8,815
  • 2
  • 30
  • 33
  • Thanks subhaze -- I'll definitely check out your link. – dngoo Jun 01 '12 at 03:56
  • No prob. Glad to see you're wanting to know 'why' something does what it does and not just blindly use "what works" and no questions asked :) Here is a blog post that has pretty good information on closures as well http://javascriptweblog.wordpress.com/2010/10/25/understanding-javascript-closures/. MDN is another good spot to fill your appetite on information https://developer.mozilla.org/en/JavaScript/Guide – subhaze Jun 01 '12 at 04:08