Il valore di “questo” all'interno del gestore mediante addEventListener
-
20-09-2019 - |
Domanda
Ho creato un oggetto JavaScript tramite la prototipazione. Sto cercando di rendere un tavolo in modo dinamico. Mentre la parte di rendering è semplice e funziona bene, ho anche bisogno di gestire determinati eventi lato client per la tavola reso in modo dinamico. Questo, è anche facile. Dove sto avendo problemi è con il "presente" di riferimento all'interno della funzione che gestisce l'evento. Al posto di "questo" fa riferimento all'oggetto, è riferimento l'elemento che ha generato l'evento.
Vedere 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);
}
Soluzione
È necessario handler "legare" per l'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 qui gestore eventi normalizza oggetto event
(passato come primo argomento) e richiama handleCellClick
in un contesto appropriato (cioè riferito ad un elemento che è stato attaccato evento ascoltatore).
Si noti inoltre che la normalizzazione contesto here (cioè modificando corretta this
in gestore di eventi) crea un riferimento circolare tra funzione utilizzata come gestore (onClickBound
) e un oggetto elemento (cell1
). In alcune versioni di IE (6 e 7) questo può, e probabilmente, portare a una perdita di memoria. Questa perdita, in sostanza, è il browser non riuscendo a liberare memoria a pagina di aggiornamento a causa di riferimento circolare esistente tra oggetto nativa e host.
Per aggirarlo, si avrebbe bisogno di uno a) rilasciare this
normalizzazione; b) impiegano alternativa (e più complesso) strategia di normalizzazione; c) "ripulire" listener di eventi esistenti a pagina scarico, vale a dire utilizzando removeEventListener
, detachEvent
ed elementi null
ing (che purtroppo avrebbe reso veloce la navigazione storia browser degli inutili).
Si potrebbe anche trovare una libreria JS che si occupa di questo. La maggior parte di loro (ad es .: jQuery, Prototype.js, YUI, ecc), di solito gestire ripuliture come descritto in (c).
Altri suggerimenti
È possibile utilizzare legano che consente di specificare il valore che dovrebbe essere utilizzato come questo per tutte le chiamate a una data 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 sopra è che non è possibile rimuovere l'ascoltatore con bind. Un'altra soluzione sta usando una funzione speciale chiamata handleEvent per catturare tutti gli eventi:
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 è la migliore :). Mi basta copiare incollato la parte di rispondere a questa domanda.
Inoltre, un altro modo è quello di utilizzare il EventListener Interface (da DOM2 !! Domandandosi perchè nessuno ha menzionato che, considerando che è il modo più pulita e significava per una simile situazione.)
cioè, invece di un passante una funzione di richiamata, si passa un oggetto che implementa EventListener interfaccia. In poche parole, significa solo si dovrebbe avere una proprietà in oggetto chiamato "handleEvent", che punta alla funzione di gestore di eventi. La differenza principale è, all'interno della funzione, this
farà riferimento all'oggetto passato al addEventListener
. Cioè, this.theTicketTable
sarà l'istanza oggetto nel belowCode. Per capire cosa intendo, guardare il codice modificato con attenzione:
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 posto più vecchio, ma si può anche semplicemente assegnare il contesto per un self
variabile, buttare la funzione in una funzione anonima che richiama la 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);
...
};
Questo funziona meglio che la "risposta accettata" perché il contesto non ha bisogno di assegnare una variabile per l'intera classe o globale, piuttosto è ben nascosto all'interno dello stesso metodo che intercetta l'evento.
Fortemente influenzato da kamathln e la risposta di Gagarine ho pensato che avrei potuto affrontare questo.
Stavo pensando probabilmente si potrebbe ottenere un po 'più libertà se si mette handeCellClick in un elenco di callback e utilizzare un oggetto utilizzando l'interfaccia EventListener sull'evento per attivare i metodi delle liste di callback con il corretto questo.
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);
}
Questa sintassi freccia funziona per me:
document.addEventListener('click', (event) => {
// do stuff with event
// do stuff with this
});
questo sarà la genitori contesto e non il documento di contesto
Che dire
...
cell1.addEventListener("click", this.handleCellClick.bind(this));
...
ticketTable.prototype.handleCellClick = function(e)
{
alert(e.currentTarget.innerHTML);
alert(this.tickets.length);
}
e.currentTarget indica il bersaglio che è legato al "click evento" (per l'elemento che ha generato l'evento), mentre
bind (questo) preserva il valore del outerscope this
all'interno della funzione evento click.
Se si desidera ottenere un obiettivo esatto fatto clic, utilizzare e.target , invece.
Con ES6, è possibile utilizzare una funzione di freccia come che utilizzerà scoping lessicale [0], che consente di evitare di dover utilizzare bind
o self = this
:
var something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // 'Something Good'
};
element.addEventListener('click', () => this.onclick1());
}