Значение "this" внутри обработчика с помощью addEventListener
Я создал объект Javascript с помощью прототипирования. Я пытаюсь визуализировать таблицу динамически. Хотя часть рендеринга проста и работает нормально, мне также нужно обрабатывать определенные события на стороне клиента для динамически отображаемой таблицы. Это также легко. У меня возникли проблемы со ссылкой "this" внутри функции, которая обрабатывает событие. вместо "this" ссылается на объект, он ссылается на элемент, вызвавший событие.
Смотрите код. Проблемная область находится в 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);
}
Ответы
Ответ 1
Вам нужно "привязать" обработчик к вашему экземпляру.
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);
}
Обратите внимание, что обработчик событий здесь нормализует объект event
(передается как первый аргумент) и вызывает handleCellClick
в соответствующем контексте (т.е. ссылается на элемент, к которому был присоединен прослушиватель событий).
Также обратите внимание, что здесь нормализация контекста (т.е. установка правильного this
в обработчике событий) создает циклическую ссылку между функцией, используемой как обработчик события (onClickBound
), и элементом элемента (cell1
). В некоторых версиях IE (6 и 7) это может и, вероятно, приведет к утечке памяти. По сути, эта утечка не позволяет браузеру освободить память при обновлении страницы из-за циклической ссылки, существующей между нативным и хост-объектом.
Чтобы обойти это, вам нужно либо: a) отказаться от this
нормализации; б) использовать альтернативную (и более сложную) стратегию нормализации; c) "очистить" существующие прослушиватели событий при разгрузке страницы, т.е. с помощью removeEventListener
, detachEvent
и элементов null
ing (что, к сожалению, сделает невозможным быструю историю навигации браузеров).
Вы также можете найти библиотеку JS, которая позаботится об этом. Большинство из них (например: jQuery, Prototype.js, YUI и т.д.) Обычно обрабатывают очистку, как описано в (c).
Ответ 2
Вы можете использовать bind, который позволяет указать значение, которое должно использоваться как это для всех вызовов данной функции.
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
}
Проблема в приведенном выше примере заключается в том, что вы не можете удалить слушателя со связью. Другое решение использует специальную функцию handleEvent, чтобы поймать любые события:
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);
}
Как всегда mdn является лучшим:). Я просто скопировал эту часть, а не ответил на этот вопрос.
Ответ 3
Кроме того, еще один способ - использовать Интерфейс EventListener (от DOM2!! Интересно, почему никто не упомянул об этом, учитывая, что это самый простой способ и предназначен для просто такая ситуация.)
I.e, вместо передачи функции обратного вызова, вы передаете объект, который реализует интерфейс EventListener. Проще говоря, это просто означает, что вы должны иметь свойство в объекте, называемом "handleEvent", что указывает на функцию обработчика событий. Основное различие здесь заключается в том, что внутри функции this
будет ссылаться на объект, переданный в addEventListener
. То есть this.theTicketTable
будет экземпляром объекта в приведенном ниже коде. Чтобы понять, что я имею в виду, внимательно посмотрите на модифицированный код:
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);
}
Ответ 4
Я знаю, что это более старая запись, но вы также можете просто назначить контекст переменной self
, бросить свою функцию в анонимной функции, которая вызывает вашу функцию с помощью .call(self)
и передает в контексте.
ticketTable.prototype.render = function(element) {
...
var self = this;
cell1.addEventListener('click', function(evt) { self.handleCellClick.call(self, evt) }, false);
...
};
Это работает лучше, чем "принятый ответ", потому что контексту не нужно назначать переменную для всего класса или глобальной, а она аккуратно спрятана в том же методе, который прослушивает событие.
Ответ 5
Этот синтаксис стрелки работает для меня:
document.addEventListener('click', (event) => {
// do stuff with event
// do stuff with this
});
это будет родительским контекстом, а не документом контекста
Ответ 6
Тяжело под влиянием ответов на каматлин и гагар, я думал, что смогу справиться с этим.
Я думал, что вы, возможно, получите немного больше свободы, если поместите handeCellClick в список обратного вызова и используйте объект, используя интерфейс EventListener в событии, чтобы вызвать методы списка обратного вызова с правильным.
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);
}
Ответ 7
Как насчет
...
cell1.addEventListener("click", this.handleCellClick.bind(this));
...
ticketTable.prototype.handleCellClick = function(e)
{
alert(e.currentTarget.innerHTML);
alert(this.tickets.length);
}
e.currentTarget указывает на цель, которая связана с "событием щелчка" (на элемент, вызвавший событие), в то время как
bind (this) сохраняет значение this
объекта в функции события click.
Если вы хотите получить точную цель, используйте вместо нее e.target.
Ответ 8
В ES6 вы можете использовать функцию стрелки, так как она будет использовать лексическую область видимости [0], что позволяет вам избежать необходимости использовать bind
или 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