La valeur de "ceci" dans le gestionnaire utilisant addEventListener

J'ai créé un objet javascript via le prototypage. J'essaie de rendre une table dynamiquement. Alors que la partie de rendu est simple et fonctionne bien, je dois également gérer certains événements côté client pour la table dynamiquement rendue. C'est aussi facile. Où j'ai des problèmes avec la référence "this" dans la fonction qui gère l'événement. Au lieu de "ceci" renvoie l'objet, il fait référence à l'élément qui a soulevé l'événement.

Voir le code. La zone problématique se trouve dans "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); } 

Vous devez "relier" 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 l'objet d' event (passé comme premier argument) et invoque handleCellClick dans un contexte approprié (c.-à-d. handleCellClick à un élément auquel était attaché l'auditeur d'événements).

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

Pour le contourner, il faudrait soit a) supprimer this normalisation; B) utiliser une stratégie de normalisation alternative (et plus complexe); C) "nettoyer" les auditeurs d'événements existants en removeEventListener page, c'est-à-dire en utilisant removeEventListener , detachEvent et les éléments null ing (ce qui rendrait inutile la navigation rapide de l' detachEvent navigateurs).

Vous pouvez également trouver une bibliothèque JS qui s'occupe de cela. La plupart d'entre eux (par exemple: jQuery, Prototype.js, YUI, etc.) traitent habituellement les nettoyages comme décrit dans (c).

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

  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 problème dans l'exemple ci-dessus est que vous ne pouvez pas supprimer l'auditeur avec bind. Une autre solution utilise 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', 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); } 

Comme toujours, mdn est le meilleur :). Je viens de copier collé la partie que de répondre à cette question.

En outre, une autre façon est d'utiliser l' interface EventListener (de DOM2! Vous vous demandez pourquoi personne ne l'a mentionné, étant donné que c'est la façon la plus soignée et conçue 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. Autrement dit, cela signifie simplement que vous devriez avoir une propriété dans l'objet appelé "handleEvent", qui indique la fonction du gestionnaire d'événements. La principale différence ici est, à l'intérieur de la fonction, this se référera à l'objet passé à l' addEventListener . C'est-à-dire que this.theTicketTable sera l'instance d'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; /* * 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); } 

Je sais que c'est un message plus ancien, mais vous pouvez simplement assigner le contexte à un self variable, lancer votre fonction dans une fonction anonyme qui invoque votre fonction avec .call(self) et passe dans le contexte.

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

Cela fonctionne mieux que la "réponse acceptée" parce que le contexte n'a pas besoin d'être affecté à une variable pour la classe entière ou globale, mais elle est soigneusement cachée dans la même méthode qui écoute l'événement.

Principalement influencé par la réponse de Kamathln et Gagarine, je pensais pouvoir m'attaquer à cela.

Je pensais que vous pourriez gagner un peu plus de liberté si vous mettez handeCellClick dans une liste de rappel et utilisez un objet à l'aide de l'interface EventListener sur l'événement pour déclencher les méthodes de liste de rappel avec ce qui est correct.

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