J'ai créé un objet javascript via le prototypage. J'essaye de rendre une table dynamiquement. Bien que la partie de rendu soit simple et fonctionne correctement, je dois également gérer certains événements côté client pour le tableau rendu dynamiquement. Cela aussi est facile. J'ai des problèmes avec la référence "this" à l'intérieur de la fonction qui gère l'événement. Au lieu de "ceci" référence à l'objet, il fait référence à l'élément qui a déclenché l'événement.
Voir le code. La zone problématique est 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 "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
(transmis comme premier argument) et appelle handleCellClick
dans un contexte approprié (c’est-à-dire faisant référence à un élément qui était attaché à l’écouteur d’événements).
Notez également que la normalisation du contexte ici (c’est-à-dire définir this
dans le gestionnaire d’événements) crée une référence circulaire entre la fonction utilisée comme gestionnaire d’événement (onClickBound
) et un objet élément (cell1
). Dans certaines versions de IE (6 et 7), cela peut entraîner et entraînera probablement une fuite de mémoire. Cette fuite est essentiellement le fait que le navigateur n'a pas libéré la mémoire lors de l'actualisation de la page en raison d'une référence circulaire existant entre l'objet natif et l'hôte.
Pour le contourner, vous devez soit: a) abandonner la normalisation this
; b) employer une stratégie de normalisation alternative (et plus complexe); c) "nettoyer" les écouteurs d’événements existants lors du déchargement de la page, c’est-à-dire en utilisant removeEventListener
, detachEvent
et des éléments null
ing (ce qui rendrait malheureusement la navigation dans l'historique rapide du navigateur inutile).
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.) gèrent généralement les opérations de nettoyage comme décrit en (c).
Vous pouvez utiliser bind qui vous permet de spécifier la valeur à utiliser comme this pour tous les appels d'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'écouteur avec bind. Une autre solution consiste à utiliser une fonction spéciale appelée handleEvent pour intercepter 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 la partie collée que de répondre à cette question.
En outre, une autre méthode consiste à utiliser l'interface EventListener (à partir de DOM2! Vous vous demandez pourquoi personne ne l'a mentionnée, sachant que c'est le moyen le plus simple et conçu pour une telle situation.)
Ie, au lieu de passer une fonction de rappel, vous transmettez un objet qui implémente l'interface EventListener. En termes simples, cela signifie simplement que vous devriez 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, dans la fonction, this
fera référence à l'objet transmis à addEventListener
. C'est-à-dire que this.theTicketTable
sera l'instance de l'objet dans le BelowCode. 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 ceci est un article plus ancien, mais vous pouvez aussi simplement assigner le contexte à une variable self
, jeter votre fonction dans une fonction anonyme qui appelle 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" car il n’est pas nécessaire d’attribuer au contexte une variable pour la classe entière ou globale, mais plutôt de le ranger dans la même méthode que celle utilisée pour l’événement.
Fortement influencé par la réponse de Kamathln et Gagarine, je pensais pouvoir m'attaquer à ce problème.
Je pensais que vous pourriez probablement gagner un peu plus de liberté si vous mettiez handeCellClick dans une liste de rappel et utilisiez un objet utilisant l'interface EventListener sur l'événement pour déclencher les méthodes de liste de rappel avec le 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);
}
Cette syntaxe de flèche fonctionne pour moi:
document.addEventListener('click', (event) => {
// do stuff with event
// do stuff with this
});
this sera le contexte parent et non le contexte document
Qu'en est-il de
...
cell1.addEventListener("click", this.handleCellClick.bind(this));
...
ticketTable.prototype.handleCellClick = function(e)
{
alert(e.currentTarget.innerHTML);
alert(this.tickets.length);
}
e.currentTarget pointe vers la cible qui est liée à "l'événement click" (à l'élément qui a déclenché l'événement) tant que
bind (this) conserve la valeur outerscope de this
dans la fonction d'événement click.
Si vous souhaitez obtenir une cible exacte sur laquelle vous avez cliqué, utilisez plutôt e.target .
Avec ES6, vous pouvez utiliser une fonction de flèche car elle utilisera la portée lexicale [0], ce qui vous évite d’avoir à 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());
}