Il valore di “this” all’interno del gestore utilizzando addEventListener

Ho creato un object javascript tramite la prototipazione. Sto cercando di rendere una tabella dynamicmente. Mentre la parte di rendering è semplice e funziona bene, devo anche gestire alcuni eventi lato client per la tabella resa dynamicmente. Anche questo è facile. Dove sto avendo problemi è con il “questo” riferimento all’interno della funzione che gestisce l’evento. Invece di “questo” fa riferimento all’object, fa riferimento all’elemento che ha generato l’evento.

Vedi il codice L’area problematica è in “ticketTable.prototype.handleCellClick = function ()”

function ticketTable(ticks) { // tickets is an array this.tickets = ticks; } ticketTable.prototype.render = function(element) { var tbl = document.createElement("table"); for ( var i = 0; i < this.tickets.length; i++ ) { // create row and cells var row = document.createElement("tr"); var cell1 = document.createElement("td"); var cell2 = document.createElement("td"); // add text to the cells cell1.appendChild(document.createTextNode(i)); cell2.appendChild(document.createTextNode(this.tickets[i])); // handle clicks to the first cell. // FYI, this only works in FF, need a little more code for IE cell1.addEventListener("click", this.handleCellClick, false); // add cells to row row.appendChild(cell1); row.appendChild(cell2); // add row to table tbl.appendChild(row); } // Add table to the page element.appendChild(tbl); } ticketTable.prototype.handleCellClick = function() { // PROBLEM!!! in the context of this function, // when used to handle an event, // "this" is the element that triggered the event. // this works fine alert(this.innerHTML); // this does not. I can't seem to figure out the syntax to access the array in the object. alert(this.tickets.length); } 

Devi “bind” il gestore alla tua istanza.

 var _this = this; function onClickBound(e) { _this.handleCellClick.call(cell1, e || window.event); } if (cell1.addEventListener) { cell1.addEventListener("click", onClickBound, false); } else if (cell1.attachEvent) { cell1.attachEvent("onclick", onClickBound); } 

Si noti che il gestore di eventi qui normalizza l’object event (passato come primo argomento) e invoca handleCellClick in un contesto appropriato (ovvero facendo riferimento a un elemento a cui è stato collegato il listener di eventi).

Si noti inoltre che la normalizzazione del contesto qui (ovvero l’impostazione corretta nel gestore di eventi) crea un riferimento circolare tra la funzione utilizzata come gestore di eventi ( onClickBound ) e un object element ( cell1 ). In alcune versioni di IE (6 e 7) questo può, e probabilmente lo farà, provocare una perdita di memoria. Questa perdita, in sostanza, è che il browser non riesce a rilasciare memoria all’aggiornamento della pagina a causa del riferimento circolare esistente tra l’object nativo e l’object host.

Per aggirarlo, è necessario a) abbandonare this normalizzazione; b) utilizzare una strategia di normalizzazione alternativa (e più complessa); c) “ripulire” i listener di eventi esistenti allo detachEvent pagina, ovvero utilizzando detachEvent , detachEvent e gli elementi null (che purtroppo renderebbero inutilizzabile la navigazione veloce nella cronologia dei browser).

Potresti anche trovare una libreria JS che si occupa di questo. La maggior parte di essi (ad esempio: jQuery, Prototype.js, YUI, ecc.) Di solito gestiscono le operazioni di pulizia come descritto in (c).

È ansible utilizzare bind che consente di specificare il valore da utilizzare come questo per tutte le chiamate a una determinata funzione.

  var Something = function(element) { this.name = 'Something Good'; this.onclick1 = function(event) { console.log(this.name); // undefined, as this is the element }; this.onclick2 = function(event) { console.log(this.name); // 'Something Good', as this is the binded Something object }; element.addEventListener('click', this.onclick1, false); element.addEventListener('click', this.onclick2.bind(this), false); // Trick } 

Un problema nell’esempio precedente è che non è ansible rimuovere il listener con bind. Un’altra soluzione sta usando una funzione speciale chiamata handleEvent per catturare qualsiasi evento:

 var Something = function(element) { this.name = 'Something Good'; this.handleEvent = function(event) { console.log(this.name); // 'Something Good', as this is the Something object switch(event.type) { case 'click': // some code here... break; case 'dblclick': // some code here... break; } }; // Note that the listeners in this case are this, not this.handleEvent element.addEventListener('click', this, false); element.addEventListener('dblclick', this, false); // You can properly remove the listners element.removeEventListener('click', this, false); element.removeEventListener('dblclick', this, false); } 

Come sempre, mdn è il migliore :). Ho appena incollato la parte che rispondo a questa domanda.

Inoltre, un altro modo è quello di utilizzare l’ interfaccia EventListener (da DOM2 !! Chiedersi perché nessuno l’ha menzionato, considerando che è il modo più ordinato e pensato per una situazione del genere).

Cioè, invece di passare una funzione di callback, si passa un object che implementa EventListener Interface. In poche parole, significa semplicemente che dovresti avere una proprietà nell’object chiamato “handleEvent”, che punta alla funzione del gestore di eventi. La differenza principale qui, all’interno della funzione, si riferirà all’object passato a addEventListener . Cioè, this.theTicketTable sarà l’istanza dell’object nel belowCode. Per capire cosa intendo, guarda attentamente il codice modificato:

 ticketTable.prototype.render = function(element) { ... var self = this; /* * Notice that Instead of a function, we pass an object. * It has "handleEvent" property/key. You can add other * objects inside the object. The whole object will become * "this" when the function gets called. */ cell1.addEventListener('click', { handleEvent:this.handleCellClick, theTicketTable:this }, false); ... }; // note the "event" parameter added. ticketTable.prototype.handleCellClick = function(event) { /* * "this" does not always refer to the event target element. * It is a bad practice to use 'this' to refer to event targets * inside event handlers. Always use event.target or some property * from 'event' object passed as parameter by the DOM engine. */ alert(event.target.innerHTML); // "this" now points to the object we passed to addEventListener. So: alert(this.theTicketTable.tickets.length); } 

So che questo è un post più vecchio, ma puoi anche semplicemente assegnare il contesto a una variabile self , lanciare la tua funzione in una funzione anonima che richiama la tua funzione con .call(self) e passa nel contesto.

 ticketTable.prototype.render = function(element) { ... var self = this; cell1.addEventListener('click', function(evt) { self.handleCellClick.call(self, evt) }, false); ... }; 

Funziona meglio della “risposta accettata” perché al contesto non è necessario assegnare una variabile per l’intera class o globale, ma è ordinatamente nascosto nello stesso metodo che ascolta l’evento.

Fortemente influenzato dalla risposta di kamathln e gagarine, pensavo di poterlo affrontare.

Stavo pensando che potresti guadagnare un po ‘più di libertà se metti handeCellClick in un elenco di callback e utilizzi un object utilizzando l’interfaccia EventListener sull’evento per triggersre i metodi dell’elenco di callback con questo corretto.

 function ticketTable(ticks) { // tickets is an array this.tickets = ticks; // the callback array of methods to be run when // event is triggered this._callbacks = {handleCellClick:[this._handleCellClick]}; // assigned eventListenerInterface to one of this // objects properties this.handleCellClick = new eventListenerInterface(this,'handleCellClick'); } //set when eventListenerInterface is instantiated function eventListenerInterface(parent, callback_type) { this.parent = parent; this.callback_type = callback_type; } //run when event is triggered eventListenerInterface.prototype.handleEvent(evt) { for ( var i = 0; i < this.parent._callbacks[this.callback_type].length; i++ ) { //run the callback method here, with this.parent as //this and evt as the first argument to the method this.parent._callbacks[this.callback_type][i].call(this.parent, evt); } } ticketTable.prototype.render = function(element) { /* your code*/ { /* your code*/ //the way the event is attached looks the same cell1.addEventListener("click", this.handleCellClick, false); /* your code*/ } /* your code*/ } //handleCellClick renamed to _handleCellClick //and added evt attribute ticketTable.prototype._handleCellClick = function(evt) { // this shouldn't work alert(this.innerHTML); // this however might work alert(evt.target.innerHTML); // this should work alert(this.tickets.length); } 

Che dire

 ... cell1.addEventListener("click", this.handleCellClick.bind(this)); ... ticketTable.prototype.handleCellClick = function(e) { alert(e.currentTarget.innerHTML); alert(this.tickets.length); } 

e.currentTarget punta alla destinazione che è legata all ‘”evento click” (all’elemento che ha generato l’evento) mentre

bind (this) conserva il valore di outerscope di this all’interno della funzione evento click.

Se vuoi ottenere un target esatto cliccato, usa invece e.target .