javascript - ts2339 - El valor de "this" dentro del controlador mediante addEventListener
la propiedad ''value'' no existe en el tipo ''htmlelement'' angular (6)
Creé un objeto javascript mediante creación de prototipos. Estoy tratando de renderizar una tabla dinámicamente. Si bien la parte de representación es simple y funciona bien, también necesito manejar ciertos eventos del lado del cliente para la tabla renderizada dinámicamente. Eso, también es fácil. Donde estoy teniendo problemas es con la referencia "this" dentro de la función que maneja el evento. En lugar de "esto" hace referencia al objeto, hace referencia al elemento que generó el evento.
Ver código. El área problemática está en "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);
}
Además, una forma más es utilizar la Interfaz EventListener (desde DOM2. Preguntarse por qué nadie lo mencionó, teniendo en cuenta que es la mejor manera y está destinado para tal situación).
Es decir, en lugar de pasar una función de devolución de llamada, pasas un objeto que implementa la interfaz EventListener. En pocas palabras, simplemente significa que debe tener una propiedad en el objeto llamado "handleEvent", que apunta a la función del controlador de eventos. La principal diferencia aquí es, dentro de la función, this
se referirá al objeto pasado al addEventListener
. Es decir, this.theTicketTable
será la instancia del objeto en el código belowCode. Para entender lo que quiero decir, mira el código modificado cuidadosamente:
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);
}
Debe "vincular" el controlador a su instancia.
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);
}
Tenga en cuenta que el controlador de eventos aquí normaliza el objeto de event
(pasado como un primer argumento) e invoca handleCellClick
en un contexto apropiado (es decir, refiriéndose a un elemento al que se adjuntó el detector de eventos).
También tenga en cuenta que la normalización de contexto aquí (es decir, estableciendo this
en el controlador de eventos) crea una referencia circular entre la función utilizada como controlador de eventos ( onClickBound
) y un objeto de elemento ( cell1
). En algunas versiones de IE (6 y 7), esto puede generar, y probablemente lo hará, una pérdida de memoria. En esencia, esta fuga es que el navegador no puede liberar memoria en la actualización de página debido a la referencia circular existente entre el objeto nativo y el objeto host.
Para eludirlo, necesitaría a) a) abandonar this
normalización; b) emplear una estrategia de normalización alternativa (y más compleja); c) "limpiar" oyentes de eventos existentes en la descarga de la página, es decir, mediante el uso de removeEventListener
, detachEvent
y elementos null
ing (lo que desafortunadamente haría que la navegación rápida del historial de los navegadores fuera inútil).
También puede encontrar una biblioteca JS que se encargue de esto. La mayoría de ellos (p. Ej., JQuery, Prototype.js, YUI, etc.) generalmente manejan las limpiezas como se describe en (c).
Muy influenciado por la respuesta de Kamathln y Gagarine, pensé que podría abordar esto.
Pensaba que probablemente podría obtener un poco más de libertad si colocaba hanCellClick en una lista de devolución de llamada y utilizaba un objeto usando la interfaz EventListener en el evento para activar los métodos de la lista de devolución de llamada con la corrección de esto.
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);
}
Puede usar bind que le permite especificar el valor que se debe usar como esto para todas las llamadas a una función dada.
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 problema en el ejemplo anterior es que no puede eliminar el oyente con bind. Otra solución es usar una función especial llamada handleEvent para atrapar cualquier 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 siempre mdn es el mejor :). Copié la parte pegada antes que responder esta pregunta.
Qué pasa
...
cell1.addEventListener("click", this.handleCellClick.bind(this));
...
ticketTable.prototype.handleCellClick = function(e)
{
alert(e.currentTarget.innerHTML);
alert(this.tickets.length);
}
e.currentTarget apunta al objetivo que está vinculado al "evento de clic" (al elemento que provocó el evento) mientras
bind (this) conserva el valor de outerscope de this
dentro de la función click event.
Si desea hacer clic en un objetivo exacto, use e.target en su lugar.
Sé que esta es una publicación más antigua, pero también puedes simplemente asignar el contexto a un self
variable, lanzar tu función en una función anónima que invoca tu función con .call(self)
y pasa en el contexto.
ticketTable.prototype.render = function(element) {
...
var self = this;
cell1.addEventListener(''click'', function(evt) { self.handleCellClick.call(self, evt) }, false);
...
};
Esto funciona mejor que la "respuesta aceptada" porque al contexto no se le debe asignar una variable para toda la clase o global, sino que está meticulosamente escondido dentro del mismo método que escucha el evento.