it-swarm.com.ru

Значение 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);
    }
49
Darthg8r

Вам нужно «привязать» обработчик к вашему экземпляру.

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) это может и, вероятно, приведет к утечке памяти. Эта утечка в сущности заключается в том, что браузер не может освободить память при обновлении страницы из-за циклической ссылки, существующей между собственным объектом и объектом Host. 

Чтобы обойти это, вам нужно: а) отбросить this нормализацию; б) использовать альтернативную (и более сложную) стратегию нормализации; c) «очистить» существующие прослушиватели событий при выгрузке страницы, т. е. с помощью removeEventListener, detachEvent и элементов nulling (что, к сожалению, сделает быструю навигацию по истории в браузерах бесполезной).

Вы также можете найти библиотеку JS, которая позаботится об этом. Большинство из них (например, jQuery, Prototype.js, YUI и т.д.) Обычно выполняют очистки, как описано в (c).

39
kangax

Вы можете использовать bind , который позволяет вам указать значение, которое следует использовать как this для всех вызовов данной функции.

   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
    }

Проблема в приведенном выше примере заключается в том, что вы не можете удалить слушателя с помощью bind. Другое решение - использовать специальную функцию 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 является лучшим :). Я просто скопировал приклеенную часть, чем ответил на этот вопрос.

59
gagarine

Кроме того, еще один способ - использовать интерфейс EventListener (из DOM2 !! Интересно, почему никто не упомянул об этом, считая, что это самый удобный способ и предназначен именно для такой ситуации.) 

Т.е. вместо передачи функции обратного вызова вы передаете объект, который реализует интерфейс EventListener. Проще говоря, это просто означает, что у вас должно быть свойство в объекте handleEvent, которое указывает на функцию-обработчик события. Основное отличие состоит в том, что внутри функции this будет ссылаться на объект, переданный addEventListener. То есть this.theTicketTable будет экземпляром объекта в нижеCode. Чтобы понять, что я имею в виду, внимательно посмотрите на измененный код:

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

Я знаю, что это старый пост, но вы также можете просто присвоить контекст переменной self, добавить вашу функцию в анонимную функцию, которая вызывает вашу функцию с помощью .call(self) и передает ее в контекст.

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

Это работает лучше, чем «принятый ответ», поскольку контексту не нужно присваивать переменную для всего класса или глобального, скорее он аккуратно спрятан в том же методе, который прослушивает событие.

5
Tom Doe

Сильно под влиянием ответа Каматлна и Гагарина я подумал, что смогу справиться с этим.

Я думал, что вы могли бы получить немного больше свободы, если бы вы поместили handeCellClick в список обратных вызовов и использовали объект с помощью интерфейса EventListener для события, чтобы вызвать методы списка обратных вызовов с правильным this.

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);
    }
1
niall.campbell

Как насчет

...
    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.

0
DevWL

С 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

0
Chris