2009-08-27 50 views
41

我通過原型創建了一個JavaScript對象。我試圖動態呈現表格。雖然渲染部分很簡單並且工作正常,但我還需要處理動態渲染表的某些客戶端事件。那也很簡單。我遇到的問題是處理事件的函數內部的「this」引用。而不是「this」引用對象,它引用引發事件的元素。使用addEventListener處理程序中的「this」的值

查看代碼。有問題的區域是「ticketTable.prototype.handleCellClick =功能()」

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

回答

31

你需要「綁定」處理程序到您的實例。

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和元素null ing(這不幸地會導致瀏覽器的快速歷史導航無用)。

你也可以找到一個JS庫來處理這個問題。它們中的大多數(例如:jQuery,Prototype.js,YUI等)通常按照(c)中的描述處理清理。

+0

var _this = this;在我的代碼的上下文去? 我需要將onClickBound(e)添加到原型嗎? – Darthg8r 2009-08-27 03:05:49

+0

在'render'中,在附加事件監聽器之前。你幾乎可以用這個代碼片段替換原始示例中的addEventListener行。 – kangax 2009-08-27 03:28:35

+0

有趣的是,你提到清理。我實際上也會在這個過程中的某個時刻銷燬這些對象。我原本打算做.innerHTML =「」;我的猜測是在這方面不好。我將如何銷燬這些表格並避免上述泄漏? – Darthg8r 2009-08-27 11:29:59

5

我知道這是一個較舊的帖子,但您也可以簡單地將上下文分配給變量self,將您的函數放入一個匿名函數中,該函數用.call(self)調用函數並傳入上下文。

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

這工作比「接受的答案」更好,因爲背景並不需要分配整個類或全局變量,而它巧妙地隱藏起來,監聽事件的相同方法中。

+1

使用ES5,您只需使用:'cell1.addEventListener('click',this.handleCellClick.bind(this));'。如果要與FF <= 5兼容,請保留最後一個參數「false」。 – tomekwi 2014-10-11 10:17:59

+0

除了''bind''不會在沒有polyfill的PhantomJS中工作。 – 2014-10-13 17:45:12

+0

問題在於,如果您有其他函數從處理程序中調用,則需要將自行傳遞給線程。 – mkey 2016-05-21 12:10:30

9

此外,另一種方式是使用EventListener Interface(從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); 
} 
+0

有點整齊,但似乎你不能'removeEventListener()'這個? – knutole 2014-09-24 04:20:22

+3

@knutole,是的,你可以。只需將對象保存在一個變量中並將該變量傳遞給'addEventListener'。你可以看這裏以供參考:http://stackoverflow.com/a/15819593/2816199。 – tomekwi 2014-10-13 18:04:39

+0

TypeScript似乎不喜歡這種語法,因此它不會將對象作爲回調編譯爲「addEventListener」的調用。該死。 – 2016-07-25 16:42:05

44

您可以使用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是最好的:)。我只是複製粘貼的部分比回答這個問題。

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.currnetTarget.innerHTML); 
     alert(this.tickets.length); 
    } 

e.currnetTarget點至與結合目標「點擊事件」(對引發事件的元素),而

bind(this)在click事件函數內保存「this」的outerscope值。

如果你想獲得確切的目標點擊使用e.target來代替。

相關問題