O valor de “this” dentro do manipulador usando addEventListener

Eu criei um object javascript via prototipagem. Estou tentando renderizar uma tabela dinamicamente. Embora a parte de renderização seja simples e funcione bem, também preciso manipular determinados events do lado do cliente para a tabela renderizada dinamicamente. Isso também é fácil. Onde estou tendo problemas é com a referência “this” dentro da function que manipula o evento. Em vez de “this” fazer referência ao object, ele está fazendo referência ao elemento que gerou o evento.

Veja o código. A área problemática está em “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); } 

Você precisa vincular o manipulador à sua instância.

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

Observe que o manipulador de events aqui normaliza o object de event (passado como um primeiro argumento) e invoca o handleCellClick em um contexto apropriado (ou seja, referindo-se a um elemento ao qual o listener de evento foi anexado).

Observe também que a normalização de contexto aqui (ou seja, definindo corretamente this no manipulador de events) cria uma referência circular entre a function usada como manipulador de events ( onClickBound ) e um object de elemento ( cell1 ). Em algumas versões do IE (6 e 7) isso pode, e provavelmente resultará em um memory leaks. Esse vazamento, em essência, é o erro do navegador em liberar memory na atualização da página devido à referência circular existente entre o object nativo e o host.

Para contorná-lo, você precisaria: a) abandonar this normalização; b) empregar estratégia de normalização alternativa (e mais complexa); c) “limpar” ouvintes de events existentes no descarregamento de página, isto é, usando removeEventListener , detachEvent e elementos detachEvent (o que infelizmente tornaria inútil a navegação rápida do histórico dos navegadores).

Você também pode encontrar uma biblioteca JS que cuida disso. A maioria deles (por exemplo: jQuery, Prototype.js, YUI, etc.) geralmente lida com limpezas como descrito em (c).

Você pode usar bind que permite especificar o valor que deve ser usado como este para todas as chamadas para uma determinada function.

  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 } 

Um problema no exemplo acima é que você não pode remover o listener com bind. Outra solução é usar uma function especial chamada handleEvent para capturar qualquer evento:

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

Como sempre mdn é o melhor :). Acabei de copiar a parte colada do que responder a esta pergunta.

Além disso, mais uma maneira é usar a Interface EventListener (do DOM2 !! Imaginando por que ninguém mencionou isso, considerando que essa é a maneira mais clara e indicada para essa situação).

Ou seja, em vez de passar uma function de retorno de chamada, você passa um object que implementa a interface EventListener. Simplificando, significa apenas que você deve ter uma propriedade no object chamado “handleEvent”, que aponta para a function do manipulador de events. A principal diferença aqui é, dentro da function, this se referirá ao object passado para o addEventListener . Ou seja, this.theTicketTable será a instância do object no belowCode. Para entender o que quero dizer, observe o código modificado com cuidado:

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

Eu sei que este é um post mais antigo, mas você também pode simplesmente atribuir o contexto a uma variável self , lançar sua function em uma function anônima que invoca sua function com .call(self) e passa no contexto.

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

Isso funciona melhor do que a “resposta aceita”, porque o contexto não precisa ser atribuído a uma variável para toda a class ou global, em vez disso, ele está bem escondido dentro do mesmo método que ouve o evento.

Fortemente influenciada pela resposta de kamathln e gagarine, pensei em abordar isso.

Eu estava pensando que você poderia ganhar um pouco mais de liberdade se você colocasse o handeCellClick em uma lista de retorno de chamada e usasse um object usando a interface EventListener no evento para acionar os methods da lista de retorno de chamada com o correto.

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

Sobre o quê

 ... cell1.addEventListener("click", this.handleCellClick.bind(this)); ... ticketTable.prototype.handleCellClick = function(e) { alert(e.currentTarget.innerHTML); alert(this.tickets.length); } 

e.currentTarget aponta para o destino que está vinculado ao “evento click” (para o elemento que gerou o evento) enquanto

bind (this) preserva o valor de outerscope this dentro da function de evento click.

Se você deseja obter um destino exato clicado, use e.target em vez disso.