111 votes

La valeur de "this" dans le gestionnaire en utilisant addEventListener

J'ai créé un objet Javascript via la prototypage. J'essaie de rendre un tableau dynamiquement. Alors que la partie rendu est simple et fonctionne bien, j'ai également besoin de gérer certains événements côté client pour le tableau rendu dynamiquement. Cela aussi est facile. Où j'ai des problèmes, c'est avec la référence "this" à l'intérieur de la fonction qui gère l'événement. Au lieu de référencer l'objet, il référence l'élément qui a déclenché l'événement.

Voir le code. La zone problématique se trouve dans ticketTable.prototype.handleCellClick = function():

function ticketTable(ticks)
{
    // les tickets sont un tableau
    this.tickets = ticks;
} 

ticketTable.prototype.render = function(element)
{
    var tbl = document.createElement("table");
    for (var i = 0; i < this.tickets.length; i++)
    {
        // créer une rangée et des cellules
        var row = document.createElement("tr");
        var cell1 = document.createElement("td");
        var cell2 = document.createElement("td");

        // ajouter du texte aux cellules
        cell1.appendChild(document.createTextNode(i));
        cell2.appendChild(document.createTextNode(this.tickets[i]));

        // gérer les clics sur la première cellule.
        // FYI, cela fonctionne uniquement dans FF, besoin de quelques lignes de code supplémentaires pour IE
        cell1.addEventListener("click", this.handleCellClick, false);

        // ajouter les cellules à la rangée
        row.appendChild(cell1);
        row.appendChild(cell2);

        // ajouter la rangée au tableau
        tbl.appendChild(row);            
    }

    // Ajouter le tableau à la page
    element.appendChild(tbl);
}

ticketTable.prototype.handleCellClick = function()
{
    // PROBLÈME !!! dans le contexte de cette fonction, 
    // lorsqu'elle est utilisée pour gérer un événement, 
    // "this" est l'élément qui a déclenché l'événement.

    // cela fonctionne bien
    alert(this.innerHTML);

    // cela ne fonctionne pas. Je n'arrive pas à trouver la syntaxe pour accéder au tableau dans l'objet.
    alert(this.tickets.length);
}

117voto

gagarine Points 1165

Vous pouvez utiliser bind qui vous permet de spécifier la valeur qui doit être utilisée comme this pour tous les appels à une fonction donnée.

   var Something = function(element) {
      this.name = 'Something Good';
      this.onclick1 = function(event) {
        console.log(this.name); // indéfini, car this est l'élément
      };
      this.onclick2 = function(event) {
        console.log(this.name); // 'Something Good', car c'est l'objet Something lié
      };
      element.addEventListener('click', this.onclick1, false);
      element.addEventListener('click', this.onclick2.bind(this), false); // Astuce
    }

Un problème dans l'exemple ci-dessus est que vous ne pouvez pas supprimer l'écouteur avec bind. Une autre solution consiste à utiliser une fonction spéciale appelée handleEvent pour attraper tous les événements :

var Something = function(element) {
  this.name = 'Something Good';
  this.handleEvent = function(event) {
    console.log(this.name); // 'Something Good', car c'est l'objet Something
    switch(event.type) {
      case 'click':
        // du code ici...
        break;
      case 'dblclick':
        // du code ici...
        break;
    }
  };

  // Notez que les écouteurs dans ce cas sont this, pas this.handleEvent
  element.addEventListener('click', this, false);
  element.addEventListener('dblclick', this, false);

  // Vous pouvez correctement supprimer les écouteurs
  element.removeEventListener('click', this, false);
  element.removeEventListener('dblclick', this, false);
}

Comme toujours mdn est le meilleur :). J'ai juste copié-collé la partie qui répond à cette question.

51voto

kangax Points 19954

Vous devez "lier" le gestionnaire à votre instance.

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

Notez que le gestionnaire d'événements normalise ici l'objet event (passé en tant que premier argument) et appelle handleCellClick dans un contexte approprié (c'est-à-dire en se référant à un élément auquel le gestionnaire d'événements a été attaché).

Notez également que la normalisation du contexte ici (c'est-à-dire le réglage du this approprié dans le gestionnaire d'événements) crée une référence circulaire entre la fonction utilisée en tant que gestionnaire d'événements (onClickBound) et un objet élément (cell1). Dans certaines versions d'IE (6 et 7), cela peut, et probablement va, entraîner une fuite de mémoire. Cette fuite est essentiellement le navigateur ne libérant pas la mémoire lors du rafraîchissement de la page en raison de la référence circulaire existant entre l'objet natif et l'objet hôte.

Pour contourner cela, vous devriez soit a) abandonner la normalisation du this; b) utiliser une stratégie de normalisation alternative (et plus complexe); c) "nettoyer" les gestionnaires d'événements existants lors du déchargement de la page, c'est-à-dire en utilisant removeEventListener, detachEvent et en mettant les éléments à null (ce qui rend malheureusement inutile la navigation rapide des historiques des navigateurs).

Vous pourriez également trouver une bibliothèque JS qui gère cela. La plupart d'entre elles (par exemple : jQuery, Prototype.js, YUI, etc.) gèrent généralement les nettoyages comme décrit en (c).

16voto

Mark Points 178

Cette syntaxe de flèche fonctionne pour moi :

document.addEventListener('click', (event) => {
  // faire des choses avec l'événement
  // faire des choses avec this
});

this sera le contexte parent et non pas le contexte document.

14voto

kamathln Points 51

Aussi, une autre façon est d'utiliser l'Interface EventListener (à partir de DOM2 !! Je me demande pourquoi personne ne l'a mentionnée, alors que c'est la façon la plus propre et conçue précisément pour une telle situation.)

C'est-à-dire, au lieu de passer une fonction de rappel, vous passez un objet qui implémente l'Interface EventListener. En d'autres termes, cela signifie simplement que vous devez avoir une propriété dans l'objet appelée "handleEvent", qui pointe vers la fonction de gestionnaire d'événements. La principale différence ici est que, à l'intérieur de la fonction, this fera référence à l'objet passé à addEventListener. Cela signifie que this.theTicketTable sera l'instance de l'objet dans le code ci-dessous. Pour comprendre ce que je veux dire, regardez attentivement le code modifié :

ticketTable.prototype.render = function(element) {
...
var self = this;

/*
 * Remarquez qu'au lieu d'une fonction, nous passons un objet. 
 * Il a une propriété/clé "handleEvent". Vous pouvez ajouter d'autres
 * objets à l'intérieur de l'objet. L'ensemble de l'objet deviendra
 * "this" lorsque la fonction sera appelée. 
 */

cell1.addEventListener('click', {
                                 handleEvent:this.handleCellClick,                  
                                 theTicketTable:this
                                 }, false);
...
};

// notez le paramètre "event" ajouté.
ticketTable.prototype.handleCellClick = function(event)
{ 

    /*
     * "this" ne fait pas toujours référence à l'élément cible de l'événement. 
     * C'est une mauvaise pratique d'utiliser 'this' pour faire référence aux cibles d'événements 
     * à l'intérieur des gestionnaires d'événements. Utilisez toujours event.target ou une propriété
     * de l'objet 'event' passé en paramètre par le moteur DOM.
     */
    alert(event.target.innerHTML);

    // "this" pointe désormais vers l'objet que nous avons passé à addEventListener. Donc :

    alert(this.theTicketTable.tickets.length);
}

9voto

Chris Points 81

Avec ES6, vous pouvez utiliser une fonction fléchée qui utilisera la portée lexicale[0] vous permettant d'éviter d'utiliser bind ou 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X