プロトタイピングにより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);
}
ハンドラーをインスタンスに「バインド」する必要があります。
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)で説明されているようにクリーンアップを処理します。
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 が最高です:)。この質問に答えるよりも、単に貼り付けた部分をコピーします。
また、もう1つの方法は、 EventListener Interface を使用することです(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);
}
私はこれが古い投稿であることを知っていますが、単にコンテキストを変数self
に割り当て、.call(self)
で関数を呼び出してコンテキストを渡す匿名関数で関数をスローすることもできます。
ticketTable.prototype.render = function(element) {
...
var self = this;
cell1.addEventListener('click', function(evt) { self.handleCellClick.call(self, evt) }, false);
...
};
コンテキストにクラス全体またはグローバルの変数を割り当てる必要はなく、イベントをリッスンする同じメソッド内にきちんと収められているため、「受け入れられた答え」よりもうまく機能します。
Kamathlnとgagarineの回答に大きく影響されたので、これに取り組むかもしれないと思いました。
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);
}
この矢印構文は私のために機能します:
document.addEventListener('click', (event) => {
// do stuff with event
// do stuff with this
});
thisはdocumentコンテキストではなくparentコンテキストになります
どう?
...
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
のアウタースコープ値を保持します。
クリックされた正確なターゲットを取得する場合は、代わりにe.targetを使用します。
ES6では、矢印関数を使用できます。これは、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());
}