webentwicklung-frage-antwort-db.com.de

Der Wert "this" innerhalb des Handlers, der addEventListener verwendet

Ich habe ein Javascript-Objekt über Prototyping erstellt. Ich versuche, eine Tabelle dynamisch zu rendern. Während der Rendering-Teil einfach ist und gut funktioniert, muss ich auch bestimmte clientseitige Ereignisse für die dynamisch gerenderte Tabelle behandeln. Das ist auch einfach. Wo ich Probleme habe, ist mit der Referenz "this" innerhalb der Funktion, die das Ereignis behandelt. Statt "dieses" referenziert das Objekt, es verweist auf das Element, das das Ereignis ausgelöst hat.

Siehe Code. Der problematische Bereich befindet sich 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);
    }
49
Darthg8r

Sie müssen den Handler an Ihre Instanz "binden".

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

Beachten Sie, dass der Ereignishandler hier das event-Objekt normalisiert (als erstes Argument übergeben) und die handleCellClick in einem richtigen Kontext aufruft (d. H. Auf ein Element verweist, an das der Ereignis-Listener angehängt wurde).

Beachten Sie auch, dass hier die Kontextnormalisierung (d. H. Das Setzen der richtigen Variable this in der Ereignisbehandlungsroutine) eine Zirkelreferenz zwischen der als Ereignisbehandlungsroutine (onClickBound) verwendeten Funktion und einem Elementobjekt (cell1) erstellt. In einigen Versionen von IE (6 und 7) kann und wird dies wahrscheinlich zu einem Speicherverlust führen. Dieses Leck ist im Wesentlichen der Browser kann den Speicher für die Seitenaktualisierung nicht freigeben, da zwischen dem nativen und dem Host-Objekt ein Zirkelverweis vorhanden ist. 

Um dies zu umgehen, müssten Sie entweder a) this Normalisierung ablegen; b) alternative (und komplexere) Normalisierungsstrategie einsetzen; c) Vorhandene Ereignis-Listener beim Entladen der Seite "bereinigen", d. h. durch Verwendung von removeEventListener, detachEvent und der Elemente nulling (was leider die schnelle Verlaufsnavigation der Browser unbrauchbar machen würde).

Sie könnten auch eine JS-Bibliothek finden, die sich darum kümmert. Die meisten von ihnen (z. B. jQuery, Prototype.js, YUI usw.) behandeln normalerweise die in (c) beschriebenen Aufräumarbeiten.

39
kangax

Sie können bind verwenden, um den Wert anzugeben, der für alle Aufrufe einer bestimmten Funktion als this verwendet werden soll.

   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
    }

Ein Problem im obigen Beispiel ist, dass Sie den Listener nicht mit Bind entfernen können. Eine andere Lösung ist die Verwendung einer speziellen Funktion namens handleEvent, um Ereignisse abzufangen:

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

Wie immer ist mdn das Beste :). Ich habe das Teil einfach kopiert und diese Frage beantwortet.

59
gagarine

Eine weitere Möglichkeit ist die Verwendung der EventListener-Schnittstelle (von DOM2 !!). Warum nicht, hat es erwähnt? 

Anstatt eine Callback-Funktion zu übergeben, übergeben Sie ein Objekt, das die EventListener-Schnittstelle implementiert. Einfach ausgedrückt bedeutet dies nur, dass Sie eine Eigenschaft im Objekt namens "handleEvent" haben sollten, die auf die Ereignisbehandlungsfunktion verweist. Der Hauptunterschied besteht darin, dass this sich innerhalb der Funktion auf das Objekt bezieht, das an die addEventListener übergeben wurde. Das heißt, this.theTicketTable ist die Objektinstanz in dem BelowCode. Um zu verstehen, was ich meine, schauen Sie sich den modifizierten Code sorgfältig an:

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

Ich weiß, dass dies ein älterer Beitrag ist, aber Sie können den Kontext auch einfach einer Variablen self zuweisen, Ihre Funktion in eine anonyme Funktion werfen, die Ihre Funktion mit .call(self) aufruft und im Kontext übergibt.

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

Dies funktioniert besser als die "akzeptierte Antwort", da dem Kontext keine Variable für die gesamte Klasse oder für die globale Klasse zugewiesen werden muss. Vielmehr wird er innerhalb derselben Methode, die auf das Ereignis wartet, ordentlich versteckt.

5
Tom Doe

Durch Kamathln und Gagarines Antwort stark beeinflusst, dachte ich, ich könnte das angehen.

Ich dachte, Sie könnten wahrscheinlich ein bisschen mehr Freiheit gewinnen, wenn Sie handeCellClick in eine Rückrufliste aufnehmen und ein Objekt verwenden, das die EventListener-Schnittstelle für das Ereignis verwendet, um die Rückruflistenmethoden mit der richtigen Option auszulösen.

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
niall.campbell

Diese Pfeilsyntax funktioniert für mich:

document.addEventListener('click', (event) => {
  // do stuff with event
  // do stuff with this 
});

this ist der parent Kontext und nicht der document Kontext

1
Mark

Wie wäre es mit

...
    cell1.addEventListener("click", this.handleCellClick.bind(this));
...

ticketTable.prototype.handleCellClick = function(e)
    {
        alert(e.currentTarget.innerHTML);
        alert(this.tickets.length);
    }

e.currentTarget zeigt auf das Ziel das an das "click event" (an das Element, das das Ereignis ausgelöst hat) gebunden ist

bind (this) behält den Outerscope-Wert von this in der Klickereignisfunktion bei.

Wenn Sie ein genaues Ziel auswählen möchten, verwenden Sie stattdessen e.target .

0
DevWL

Mit ES6 können Sie eine Pfeilfunktion verwenden, die lexikalisches Scoping [0] verwendet, wodurch Sie die Verwendung von bind oder self = this vermeiden können:

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

0
Chris