简体   繁体   中英

How can I get an object inside a javascript event handler?

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?

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.

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);
  };
};  

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM