使用addEventListener的处理程序中“ this”的值


77

我已经通过原型创建了一个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);
    }

Answers:


46

您需要将处理程序“绑定”到您的实例。

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规范化;b)采用替代(且更为复杂)的标准化策略;在页面卸载,即C)“清理”现有的事件侦听器使用removeEventListenerdetachEvent和元素null荷兰国际集团(不幸的是会使浏览器的快速历史浏览没用)。

您还可以找到一个负责此工作的JS库。它们中的大多数(例如:jQuery,Prototype.js,YUI等)通常按照(c)中所述进行清理。


var _this = this在哪里?在我的代码上下文中去了?我需要在原型中添加onClickBound(e)吗?
2009年

在中render,在附加事件侦听器之前。您几乎可以addEventListener用此代码段替换原始示例中的行。
kangax

提到清理很有趣。实际上,我还将在此过程中的某个时刻销毁这些对象。我原本打算只做.innerHTML =“”; 我的猜测是在这种情况下不好。我将如何销毁该表并避免上述泄漏?
Darthg8r

正如我之前说过的,请查看removeEventListener/detachEvent并打破循环引用。这是对泄漏的一个很好的解释-jibbering.com/faq/faq_notes/closures.html#clMem
kangax 2009年

4
我不知道为什么,但是这种自我=这个窍门在我看来总是不对的。
gagarine

84

您可以使用绑定,它可以让你指定要用作值对于一个给定函数的调用。

   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是最好的:)。我只是复制粘贴的部分,而不是回答这个问题。


10

另外,另一种方法是使用EventListener接口(来自DOM2 !!不知道为什么没有人提到它,考虑到这是最简洁的方法,并且仅适用于这种情况)。

即,您传递一个实现EventListener接口的对象,而不是传递回调函数。简而言之,它只是意味着您应该在名为“ handleEvent”的对象中具有一个属性,该属性指向事件处理程序函数。这里的主要区别是,在函数内部,this将引用传递给的对象addEventListener。也就是说,this.theTicketTable将是belowCode中的对象实例。要理解我的意思,请仔细查看修改后的代码:

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

金达整洁,但似乎你不能removeEventListener()这样做?
knutole 2014年

3
@knutole,是的,可以。只需将对象保存在变量中,然后将变量传递给即可addEventListener。您可以在此处查找参考:stackoverflow.com/a/15819593/2816199
tomekwi 2014年

TypeScript似乎不喜欢这种语法,因此它不会编译以addEventListener对象作为回调的调用。该死
David R Tribble

@DavidRTribble您是否尝试将对象分配给变量,然后传递变量?还没有亲自尝试过TypeScript,但是,如果这只是语法问题,而不是“函数不接受参数”类型的问题,那么这可能是一个解决方案
kamathln


6

此箭头语法对我有用:

document.addEventListener('click', (event) => {
  // do stuff with event
  // do stuff with this 
});

将是上下文而不是文档上下文


5

我知道这是一篇较旧的文章,但是您也可以简单地将上下文分配给变量self,将函数扔到匿名函数中,该匿名函数使用.call(self)上下文并在上下文中传递函数。

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

这比“可接受的答案”更好,因为不需要为整个类或全局变量分配上下文,而是将其整齐地塞在侦听事件的同一方法中。


2
使用ES5,您只需使用即可获得相同的效果cell1.addEventListener('click', this.handleCellClick.bind(this));false如果要与FF <= 5兼容,请保留最后一个参数。
tomekwi 2014年

这样做的问题是,如果您从处理程序中调用了其他函数,则需要将self向下传递。
mkey

这是我使用的解决方案。我本来希望使用该.bind()方法,但不得不使用它,.call()因为我们的应用程序必须支持IE8一段时间。
David R Tribble

1
以前我用过self,直到发现self与以前一样window,现在我用了me
钱琛

1
您如何使用removeEventListener这种方式?
Tamb

1

受kamathln和gagarine回答的影响很大,我想我可以解决这个问题。

我当时想,如果将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);
    }

0

关于什么

...
    cell1.addEventListener("click", this.handleCellClick.bind(this));
...

ticketTable.prototype.handleCellClick = function(e)
    {
        alert(e.currentTarget.innerHTML);
        alert(this.tickets.length);
    }

e.currentTarget指向绑定到“点击事件”的目标(指向引发事件的元素),而

bind(this)保留thisclick事件函数内部的externalscope值。

如果要单击准确的目标,请改用e.target

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.