2009-08-27 5 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を呼び出すこと。

は、イベントハンドラ(onClickBound)と要素オブジェクト(cell1)として使用される関数との間の循環参照を作成する(すなわち、イベントハンドラに適切this設定)ここでそのコンテキストの正規化に注意してください。 IE(6と7)のいくつかのバージョンでは、これはおそらくメモリリークを引き起こす可能性があります。このリークは、ネイティブとホストオブジェクトの間に循環参照が存在するため、ブラウザがページリフレッシュ時にメモリを解放できないことにあります。

これを回避するには、a)thisの正規化を削除するか、 b)代替(より複雑な)正規化戦略を採用する。 c)removeEventListener,detachEvent、および要素nullを使用してページのアンロード時に既存のイベントリスナーを「クリーンアップ」します(残念なことに、ブラウザーの高速履歴ナビゲーションは役に立たなくなります)。

これを処理するJSライブラリもあります。それらのほとんど(例:jQuery、Prototype.js、YUIなど)は、通常、(c)で説明したようにクリーンアップを処理します。

+0

ここで、var _this = this;私のコードのコンテキストで行く? プロトタイプにonClickBound(e)を追加する必要がありますか? – Darthg8r

+0

'render'では、イベントリスナーをアタッチする直前です。元の例の 'addEventListener'行をこのスニペットで置き換えることができます。 – kangax

+0

あなたがきれいに言及するのは興味深いことです。実際には、これらのオブジェクトをプロセスのある時点で破棄しています。もともと私はちょうど.innerHTML = "";私の推測は、この文脈では悪いことです。どのようにこのテーブルを破壊し、前述の漏れを避けるでしょうか? – Darthg8r

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

+0

polyfillなしでPhantomJSでは 'bind'は動作しません。 –

+0

この問題は、ハンドラから呼び出された他の関数がある場合は、selfを渡す必要があることです。 – mkey

9

また、もう一つの方法は、EventListener Interfaceを使用することです(誰もそれがneatest方法、ちょうどそのような状況のためのもので考えると、それを言及していない理由を疑問に思う!! DOM2から。)の代わりに、

すなわち、コールバック関数を渡すと、EventListener Interfaceを実装するオブジェクトを渡します。簡単に言えば、イベントハンドラ関数を指す "handleEvent"というオブジェクトのプロパティを持つ必要があるということです。主な違いは、関数内でthisaddEventListenerに渡されたオブジェクトを参照します。つまり、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); 
} 
+0

きちんとしていますが、これで 'removeEventListener()'できないようですね。 – knutole

+3

@knutoleはい、できます。変数にオブジェクトを保存し、その変数を 'addEventListener'に渡してください。参考までにこちらをご覧ください:http://stackoverflow.com/a/15819593/2816199 – tomekwi

+0

TypeScriptはこの構文が気に入らないので、コールバックとしてオブジェクトを持つ 'addEventListener'の呼び出しをコンパイルしません。ダメージ。 –

44

あなたが与えられた関数に対するすべての呼び出しのためのこのとして使用されるべき値を指定することができますbindを使用することができます。

上記の例の問題は、bindを使用してリスナーを削除できないことです。別の解決策は、すべてのイベントをキャッチするのhandleEventと呼ばれる特殊な機能を使用している:常にmdnよう

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

が最高:)です。私はちょうどこの質問に答えるよりも部分を貼り付けコピーします。

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

何e.currnetTarget

... cell1.addEventListener("click", this.handleCellClick.bind(this)); ... ticketTable.prototype.handleCellClick = function(e) { alert(e.currnetTarget.innerHTML); alert(this.tickets.length); } 

について
ポイント(イベントを発生させた要素に) "イベントをクリックして" と結合されたターゲット

ながら、 bind(this) clickイベント関数内で "this"の外側スコープ値を保持します。

正確なターゲットをクリックして使用する場合はeターゲットを代わりに使用してください。

関連する問題