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);
    }
È stato utile?

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

[0] https://medium.freecodecamp.org/learn-es6-the-dope-way-part-ii-arrow-functions-and-the-this-keyword-381ac7a32881

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top