Domanda Il valore di "this" all'interno del gestore utilizzando addEventListener


Ho creato un oggetto javascript tramite la prototipazione. Sto cercando di rendere una tabella dinamicamente. Mentre la parte di rendering è semplice e funziona bene, devo anche gestire alcuni eventi lato client per la tabella resa dinamicamente. 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'oggetto, 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);
    }

44
2017-08-27 02:44


origine


risposte:


Devi "legare" 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);
}

Nota che il gestore eventi qui si normalizza event oggetto (passato come primo argomento) e invoca handleCellClick in un contesto appropriato (ad esempio facendo riferimento a un elemento a cui è stato collegato un listener di eventi).

Si noti inoltre che la normalizzazione del contesto qui (vale a dire l'impostazione corretta this nel gestore di eventi) crea un riferimento circolare tra la funzione utilizzata come gestore di eventi (onClickBound) e un oggetto elemento (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'oggetto nativo e l'oggetto host.

Per aggirarlo, è necessario a) cadere this normalizzazione; b) utilizzare una strategia di normalizzazione alternativa (e più complessa); c) "ripulire" i listener di eventi esistenti allo scaricamento della pagina, cioè usando removeEventListener, detachEvent ed elementi nulling (che sfortunatamente renderebbe inutile la navigazione veloce nella storia 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).


35
2017-08-27 02:53



Puoi usare legare che ti permette di specificare il valore che dovrebbe essere usato 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 è possibile rimuovere il listener con bind. Un'altra soluzione sta usando una funzione speciale chiamata handleEvent per prendere 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.


50
2017-10-22 00:31



Inoltre, un altro modo è usare il EventListener Interface (da DOM2 !! Mi chiedo perché nessuno l'abbia menzionato, considerando che è il modo più ordinato e pensato per una situazione del genere.)

I.e, invece di passare una funzione di callback, si passa un oggetto che implementa l'interfaccia EventListener. In poche parole, significa semplicemente che dovresti avere una proprietà nell'oggetto chiamato "handleEvent", che punta alla funzione del gestore di eventi. La principale differenza qui è, all'interno della funzione, this si riferirà all'oggetto passato al addEventListener. Questo è, this.theTicketTable sarà l'istanza dell'oggetto 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);
}

10
2018-02-15 04:27



So che questo è un post più vecchio, ma puoi anche semplicemente assegnare il contesto a una variabile self, lancia la tua funzione in una funzione anonima che invoca 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 classe o globale, ma è ordinatamente nascosto nello stesso metodo che ascolta l'evento.


5
2018-04-24 23:26



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 oggetto utilizzando l'interfaccia EventListener sull'evento per attivare 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);
    }

1
2017-09-18 02:29



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 al bersaglio che è legato all '"evento click" (all'elemento che ha generato l'evento) while

bind (questo) preserva il valore di outscope di this all'interno della funzione evento click.

Se vuoi ottenere un target esatto cliccato, usa e.target anziché.


0