umps
umps

Reputation: 1199

Javascript,Setting up onclick method syntax

I am looking at a javascript code that manipulates an HTML A tag , and I'm having trouble understanding how it sets up the "onclick" property. It seems to be telling it to update ytplayer_playitem with the index variable j and then call ytplayer_playlazy(1000)

But what's up with all the parentheses? What details in the javascript syntax allows it to be setup like this?

var a = document.createElement("a");
a.href = "#ytplayer";
a.onclick = (function (j) {
    return function () {
        ytplayer_playitem = j;
        ytplayer_playlazy(1000);
    };
})(i);

Upvotes: 2

Views: 8742

Answers (5)

Mark Reed
Mark Reed

Reputation: 95242

Well, basically, the value of onclick is a function that will get called when the element is clicked. Whatever you want to happen when the user clicks the element goes in the body of the function.

You could create a named function and then assign it to the element's onclick attribute:

function youClickedMe() {
  ...
}
a.onclick = youClickedMe

but that clutters up the namespace with a function name that is never referenced anywhere else. It's cleaner to create an anonymous function right where you need it. Normally, that would look like this:

a.onclick = function() { ... }

But if we try that with your specific example:

a.onclick = function() { 
  ytplayer_playitem = something; // ??
  ytplayer_playlazy(1000); 
}

We see that it hard-codes the something that gets played. I'm assuming the original code was taken from a loop which generates several clickable links to play; with the code just above, all of those links would play the same thing, which is probably not what you want.

So far, so straightforward, but this next leap is where it gets tricky. The solution seems obvious: if you're in a loop, why not just use the loop variable inside the function body?

// THIS DOESN'T WORK
a.onclick = function() { 
  ytplayer_playitem = i; 
  ytplayer_playlazy(1000); 
}

That looks like it should work, but unfortunately the i inside the function refers to the value of the variable i when the function is called, not when it's created. By the time the user clicks on the link, the loop that created all the links will be done and i will have its final value - probably either the last item in the list or one greater than that item's index, depending on how the loop is written. Whatever its value is, you once again have the situation where all links play the same item.

The solution in your code gets a little meta, by using a function whose return value is another function. If you pass the loop control variable to the generating function as an argument, the new function it creates can reference that parameter and always get the value that was originally passed in, no matter what has happened to the value of the outer argument variable since:

function generate_onclick(j) {
    // no matter when the returned function is called, its "j" will be 
    // the value passed into this call of "generate_onclick"
    return function() { ytplayer_playitem = j; ytplayer_playlazy(1000); }
}

To use that, call it inside the loop like this:

a.onclick = generate_onclick(i);

Each generated function gets its very own j variable, which keeps its value forever instead of changing when i does. So each link plays the right thing; mission accomplished!

That's exactly what your posted original code is doing, with one small difference: just like the first step in my explanation, the author chose to use an anonymous function instead of defining a named one. The other difference here is that they are also calling that anonymous function immediately after defining it. This code:

a.onclick = (function (j) { ... })(i)

is the anonymous version of this code:

function gen(j) { ... }
a.onclick = gen(i)

The extra parens around the anonymous version are needed because of JavaScript's semicolon-insertion rules; function (y) {...}(blah) compiles as a standalone function definition followed by a standalone expression in parentheses, rather than a function call.

Upvotes: 5

Jordan Running
Jordan Running

Reputation: 106027

a.onclick = (function (j) {
  return function () {
      ytplayer_playitem = j;
      ytplayer_playlazy(1000);
  };
})(i);

What you have here is a self-invoking anonymous function. Let's break it down, first replacing the body of the function with something simpler (return j + 1;):

function( j ) { return j + 1; }

This s a run-of-the-mill anonymous function or closure. This line of code is an expression, and so it has a value, and that value is a function. Now we could do this:

var foo = function( j ) { return j + 1; }

foo( 5 );  // => 6

You recognize this, I'm sure—we're assigning the anonymous function to the variable foo, and then calling the function by name with the argument i. But, instead of creating a new variable, because the closure is an expression we can call it like this instead:

( function( j ) { return j + 1; } )( 5 );  // => 6

Same result. Now, it's just returning j + 1 but in your code it returns something else: Another anonymous function:

return function() { /* ... */ }

What happens when we have a self-invoking anonymous function that returns a function? The result is the "inner" function that was returned:

a.onclick = ( function( j ) {
    return function() {
             ytplayer_playitem = j;
             ytplayer_playlazy( 1000 );
           }
  }
)( i );

If i was equal to 9 then a.onclick would now hold a function equivalent to this:

function() {
  ytplayer_playitem = 9;
  ytplayer_playlazy( 1000 );
}

As others have pointed out, the usefulness of this is that when ( function( j ) { /* ... */ } )( i ) is invoked you are capturing the value of i at that time and putting it into j rather than creating a reference to the value i holds, which may (and probably will) change later on.

Upvotes: 0

user1106925
user1106925

Reputation:

"But what's up with all the parentheses? "

Most of the parentheses are just doing what you'd expect.

There's an extra set that isn't technically needed, but is often used as a hint that the function is being invoked.

                       // v-v---these are part of the function definition like normal
    a.onclick = (function (j) {
           //   ^-----------this and...v
        return function () {
            ytplayer_playitem = j;
            ytplayer_playlazy(1000);
        };
 //  v---...this are technically not needed here, but are used as a hint
    })(i);
 //   ^-^---these invoked the function like normal

"What details in the javascript syntax allows it to be setup like this?"

The upshot is that the function is invoked immediately, and passed i so that its value is referenced by the j parameter in the immediately invoked function.

This creates a variable scope that the returned function will continue to have access to. This way it always has access to the j variable, and not the i that gets overwritten in the loop.


These inlined functions are abused a bit IMO. It becomes clearer if you simply make it a named function.

for(var i = 0; i < 10; i++) {
    // create the new element
    a.onclick = createHandler(i);
    // append it somewhere
}

function createHandler (j) {
    return function () {
        ytplayer_playitem = j;
        ytplayer_playlazy(1000);
    };
}

The resulting handler is exactly the same, but the code is much less cryptic.

Upvotes: 3

Joseph
Joseph

Reputation: 119837

a.onclick = (function (j) {
    return function () {
        ytplayer_playitem = j;
        ytplayer_playlazy(1000);
    };
})(i);

This creates a "closure" to ensure that the value of i that is bound to the handler is the value of i "at that time" and not i in the general.

In your code, the function inside the () is an expression, executed and passed the variable i. This is the (i) you see in the end part. In this executed function expression, the i becomes the local variable j. This executed function expression returns the handler function that is to be bound the onclick event carrying the value of j which was i "at that time"


if i did not use the closure:

//suppose i is 1
var i = 1;

a.onclick = function () {
    ytplayer_playitem = i;
    ytplayer_playlazy(1000);
};

//and changed i
i = 2;

//if you clicked the <a>, it would not be 1 onclick but 2 because you 
//did not assign the value of i "at that time". i is "tangible" this way

Upvotes: 1

lonesomeday
lonesomeday

Reputation: 237845

Right, I'm going to guess that the surrounding code looks like this:

for (var i = 0; i < playitems.length; i++) {
    // above code here
}

Now, you could do the obvious thing here, and assign the onclick property like this:

a.onclick = function() {
    ytplayer_playitem = i;
    ytplayer_playlazy(1000);
};

However that wouldn't work very well, because the value of i changes. Whichever link was clicked, the last one would be the one activated, because the value of i at that point would be the last one in the list.

So you need to prevent this happening. You need to do this by creating a new scope, which is done by creating an extra function, which is immediately invoked:

(function (j) {
    // some code here
})(i);

Because i has been passed into the function, the value is passed rather than a reference to the variable being kept. This means that you can now define a function which will have a reference to the correct value. So you get your extra function to return the click handling function:

a.onclick = (function (j) { // j is the right number and always will be
   return function () { // this function is the click handler
       ytplayer_playitem = j;
       ytplayer_playlazy(1000);
   };
})(i);

So each a element has its own click handler function, each of which has its own individual j variable, which is the correct number. So the links, when clicked, will perform the function you want them to.

Upvotes: 1

Related Questions