5

I am trying to get an object inside the onclick event handler function.

But it is not working the way I expect it to.

For example, if I run this code:

var entries = [{id: 1},{id: 2},{id: 3}];

for (var i = 0; i < entries.length; i++) {

    var entry = entries[i];

    document.getElementById(entry.id).onclick = function () { 
        console.log("this.id: " + this.id);
        console.log("entry.id: " + entry.id);
    };

}

What I expect is:

this.id: 1
entry.id: 1

this.id: 2
entry.id: 2

this.id: 3
entry.id: 3

But what I get is:

this.id: 1
entry.id: 3

this.id: 2
entry.id: 3

this.id: 3
entry.id: 3

Why is the entry object always the entry with the id 3?

How can I get the correct entry object inside the click event handler?

1
  • 1
    You're already getting the correct value via the event.target.id and event.currentTarget.id attributes. The console is receiving an entry value of 3 because that's the last iteration in the for loop. Commented Jan 19, 2011 at 3:36

3 Answers 3

6

One way to fix this is:

var entries = [{id: 1},{id: 2},{id: 3}];

for (var i = 0; i < entries.length; i++) {

    var entry = entries[i];

    document.getElementById(entry.id).onclick = (function (id) {
        return function () { 
            console.log("this.id: " + this.id);
            console.log("entry.id: " + id);
        };
    })(entry.id);

}

You can use a self-executing function (whose purpose is to provide a closure that entry.id will be bound to) to return you a new onclick handler that is bound to the particular id.

If you don't do this, all your onclick handlers get bound to the same entry variable and end up with the last value it received during the for loop.

Sign up to request clarification or add additional context in comments.

Comments

2

This is because of the way closures work in JavaScript. The onclick reference on all the 3 elements store the same function which after the loop has entry pointing to last entry. Hence the result. You can get your result by wrapping it in another function having the needed entry. Following is one way to do it:

var entries = [{id: 1},{id: 2},{id: 3}];

for (var i = 0; i < entries.length; i++) {

    var entry = entries[i];

    function setOnClickHandler(entry) {
        document.getElementById(entry.id).onclick = function () { 
            console.log("this.id: " + this.id);
            console.log("entry.id: " + entry.id);
        };  
    }

    setOnClickHandler(entry);
}

I've written a small post on this.

1 Comment

All the solutions proposed here work fine, but I like your solution more because I find it more clean and easy to understand.
2

This is one of the tricky things about closure. You are creating those event handlers with a closure over entry. They all have access to that variable, that exact variable. Consequently once those events fire they get only the last value set to entry. You need to break the closure. Here is one way.

function addClickHandlers() {
  var entries = [{id: 1},{id: 2},{id: 3}];
  var i = entries.length;
  while (i--) {
    var entry = entries[i];
    document.getElementById(entry.id).onclick = getClickHandler(entry);
  }
}  

function getClickHandler(entry) {
  return function() { 
    console.log("this.id: " + this.id);
    console.log("entry.id: " + entry.id);
  };
}

Note: I changed your for loop into a while just because it's the fastest way to loop in javascript when order isn't important and I think it's slick.

Added for completeness

There are some other methods designed to deal with this situation. I find that I use them all the time.

In Ext there is createDelegate

myhandler.createDelegate(scope, [arguments], [appendArgs]);

For javascript 1.8.5 there is Function.prototype.bind

myhandler.bind(scope, arguments)

Writing Function.prototype.bind is pretty easy (lifted from here)

Function.prototype.bind = function(self, var_args) {
  var thisFunc = this;
  var leftArgs = Array.slice(arguments, 1);
  return function(var_args) {
    var args = leftArgs.concat(Array.slice(arguments, 0));
    return thisFunc.apply(self, args);
  };
};  

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.