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

IT技术 javascript oop dom-events
2021-02-08 21:39:42

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

您可以使用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
    }

上面示例中的一个问题是您无法使用绑定删除侦听器。另一种解决方案是使用一个名为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是最好的:)。我只是复制粘贴了部分而不是回答这个问题。

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

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) 在页面卸载时“清理”现有的事件侦听器,即通过使用removeEventListener,detachEvent和 elements nulling(不幸的是,这会使浏览器的快速历史导航无用)。

您还可以找到处理此问题的 JS 库。它们中的大多数(例如:jQuery、Prototype.js、YUI 等)通常按照 (c) 中的描述处理清理。

你提到清理很有趣。我实际上也会在这个过程中的某个时刻销毁这些对象。我原本打算只做 .innerHTML = ""; 我的猜测是在这种情况下这是不好的。我将如何销毁这些表并避免提到的泄漏?
2021-03-20 21:39:42
正如我之前所说,查看removeEventListener/detachEvent并打破循环引用。这是泄漏的一个很好的解释 - jibbering.com/faq/faq_notes/closures.html#clMem
2021-03-23 21:39:42
var _this = this 在哪里?在我的代码上下文中去?我需要在原型中添加 onClickBound(e) 吗?
2021-03-28 21:39:42
我不知道为什么,但是这个自我=这个技巧在我看来总是错误的。
2021-04-01 21:39:42
render,就在附加事件侦听器之前。您几乎可以addEventListener使用此代码段替换原始示例中的行。
2021-04-04 21:39:42

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

即,不是传递回调函数,而是传递一个实现 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);
}
@knutole,是的,你可以。只需将对象保存在变量中并将变量传递给addEventListener. 您可以在此处查看参考:stackoverflow.com/a/15819593/2816199
2021-03-27 21:39:42
@DavidRTribble 您是否尝试将对象分配给变量然后传递变量?没有亲自尝试过TypeScript,但如果它确实是一个语法问题而不是“函数不接受参数”类型的问题,那么它可能是一个解决方案
2021-03-31 21:39:42
TypeScript 似乎不喜欢这种语法,因此它不会编译addEventListener使用对象作为回调的调用。该死的。
2021-04-02 21:39:42
有点整洁,但似乎你不能removeEventListener()用这个?
2021-04-03 21:39:42

这个箭头语法对我有用:

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

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

你如何用这个 removeEventListener ?
2021-03-11 21:39:42
@pixelearth 您不能将箭头函数引用到变量并在添加和删除中引用该变量吗?
2021-03-14 21:39:42
@KorayTugay 是的,你可以,但我指出这个答案的结构方式你不能。
2021-04-03 21:39:42

在 ES6 中,您可以使用箭头函数,因为它将使用词法作用域 [0],这样您就可以避免使用bindor 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

你如何用这个 removeEventListener ?
2021-03-16 21:39:42