JavaScript イベントハンドラの this について [JavaScript]
◆JavaScript イベントハンドラの this について
いや~、JavaScript のイベント関連のエントリーだけで、ずいぶん記事が書けるもんですね;;;。
今回は、けっこう JavaScript の中ではよく使う、this の注意点をメモします。
"this" というのは、自分自身のインスタンスを指す・・・・・・というのが、JavaScript の中での普通の使い方です。
例えば、Element クラスのメソッド内で処理をしている場合、通常は this はその element インスタンスを指します。
しかし、イベントハンドラは例外です。
◆今回のひとこと
JavaScript のイベントハンドラ(イベントリスナー)内では、this は使うな!
イベントハンドラ内の "this" も、ブラウザによって扱いが「バラバラ」です。
attachEvent(evt, func) や、addEventHandler(evt, func, cap) でイベントハンドラが登録された場合、evt に該当するイベントが発生すると、イベントハンドラ func が呼ばれます。
例えば、こんな例です。
・・・・・・
function onButtonClick(evt) {
alert(this);
}
var elm = document.getElementById('id_button');
elm.attachEvent('onclick', onButtonClick);
・・・・・・
<body>
<input id="id_button" type="button" value="クリック!" />
・・・・・・
これは、Internet Explorer と Opera 向けのスクリプトです。
画面に [クリック!] というボタンが表示され、このボタンをクリックすると、onButtonClink イベントハンドラが起動します。
このような形でイベントハンドラが起動されると、イベントハンドラ(ここでは onButtonClink)の第一引数(ここでは evt)に、組み込みクラスの Event クラス型のインスタンスが渡されます。
これは、俺がそういう風にスクリプトを組んでいるのではなく、JavaScript は、そういう仕組みなんですね。
このイベント インスタンス自体も厄介なんですが、それについては別のエントリーにします。
さて。
今回、上記のソースでは、イベントハンドラ onButtonClick の中で、alert(this); で this をダイアログ ボックスに表示します。
はたして、この this は、誰なのか?
[クリック!] ボタンのイベントハンドラなのだから、当然 this は [クリック!] ボタンだろう・・・・・・というのが自然の発想だと思いますが、実際は違います。
イベントハンドラの中の this が示すインスタンスは、ユーザーエージェント(ブラウザ)依存なんです。
先に結論から書いておくと、window、document、element(ボタン)の各クリックイベント(onclick)ハンドラ内で this の型を調べると、こうなってます(Internet Explorer 8 の window だけはクリックイベントを拾えないので、onload で調べました)。
◆イベントハンドラの this で取得できるクラス インスタンスブラウザ | windowクリック | document クリック | button クリック |
---|---|---|---|
Internet Explorer 8 | - (Window) | Window | Window |
Firefox 3.6.12 | Window | HTMLDocument | HTMLInputElement |
Opera 9.64 | Window | HTMLDocument | HTMLInputElement |
Safari 5.0.3 | DOMWindow | HTMLDocument | HTMLInputElement |
Google Chrome 8.0.552.215 | DOMWindow | HTMLDocument | HTMLInputElement |
バラバラやん・・・・・・。
だから、せっかくイベントハンドラの登録や開放で各ブラウザの違いを吸収しても、肝心のイベントハンドラ内で this を使った処理を書いてしまうと、これまたブラウザによって動きが変わっちゃうんです。
以下は、今回この調査に使った HTML ソースです。
例によって、このソースをローカルで HTML ファイルにしてブラウザにドロップすれば、実際の動作を確認できます。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>JavaScript EventHandler this</title>
<script type="text/javascript">
//<![CDATA[
// 環境エラー
function errorEnvironment() {
alert('ご使用の環境は、このスクリプトではサポートされません。');
}
// イベントを追加する addEvent
function addEvent(evt, func, cap) {
if (this.addEventListener)
this.addEventListener(evt, func, cap);
else if (this.attachEvent)
this.attachEvent('on' + evt, func);
else
errorEnvironment();
}
Object.prototype.addEvent = addEvent;
HTMLDocument.prototype.addEvent = addEvent;
Element.prototype.addEvent = addEvent;
// イベントを削除する removeEvent
function removeEvent(evt, func, cap) {
if (this.removeEventListener)
this.removeEventListener(evt, func, cap);
else if (this.detachEvent)
this.detachEvent('on' + evt, func);
else
errorEnvironment();
}
Object.prototype.removeEvent = removeEvent;
HTMLDocument.prototype.removeEvent = removeEvent;
Element.prototype.removeEvent = removeEvent;
// 小さい関数群
function getWinText() { return document.getElementById('win'); }
function getDocText() { return document.getElementById('doc'); }
function getBtnText() { return document.getElementById('btn'); }
function getButton(intNum) { return document.getElementById('Button' + String(intNum)); }
// input text のクリア
function onClearText(evt) {
getWinText().value = '';
getDocText().value = '';
getBtnText().value = '';
}
// ウィンドウのクリック イベント
function onWindowClick(evt) { getWinText().value = this; }
// ドキュメントのクリック イベント
function onDocumentClick(evt) { getDocText().value = this; }
// ボタンのクリック イベント
function onButtonClick(evt) { getBtnText().value = this; }
// イベントの登録状態
var isAdded = false;
// イベントの一括登録
function addAllEvents(evt) {
if (!window.isAdded) {
window.addEvent('click', onWindowClick, false);
document.addEvent('click', onDocumentClick, false);
getButton(1).addEvent('click', onButtonClick, false);
}
window.isAdded = true;
getButton(2).disabled = window.isAdded;
getButton(3).disabled = (!window.isAdded);
onClearText(null);
}
// イベントの一括解放
function removeAllEvents(evt) {
if (window.isAdded) {
window.removeEvent('click', onWindowClick, false);
document.removeEvent('click', onDocumentClick, false);
getButton(1).removeEvent('click', onButtonClick, false);
}
window.isAdded = false;
getButton(2).disabled = window.isAdded;
getButton(3).disabled = (!window.isAdded);
onClearText(null);
}
// ウィンドウのロード イベント
function onWindowLoad(evt) {
window.removeEvent('load', onWindowLoad, true);
window.addEvent('unload', onWindowUnload, true);
addAllEvents(null);
}
// ウィンドウのアンロード イベント
function onWindowUnload(evt) {
window.removeEvent('unload', onWindowUnload, true);
removeAllEvents(null);
}
// 唯一の初期化
window.addEvent('load', onWindowLoad, true);
//]]
</script>
</head>
<body>
<div style="width:480px;border:solid 1px Black;">
<input id="Button1" type="button" value="ボタン" />
<input id="Button2" type="button" value="イベントの一括登録" onclick="addAllEvents(null);" />
<input id="Button3" type="button" value="イベントの一括削除" onclick="removeAllEvents(null);" />
<input id="Button4" type="button" value="テキストのクリア" onclick="onClearText(null);" />
<table style="width:100%;">
<tr style="width:100%;">
<td style="width:30%;">window onclick</td>
<td style="width:70%;"><input id="win" type="text" style="width:99%;" /></td>
</tr>
<tr>
<td>document onclick</td>
<td><input id="doc" type="text" value="" style="width:99%;" /></td>
</tr>
<tr>
<td>button onclick</td>
<td><input id="btn" type="text" value="" style="width:99%;" /></td>
</tr>
</table>
</div>
</body>
</html>
画面構成は、ブラウザごとのイベントハンドラの違いを吸収する removeEvent エントリーのサンプルとほぼ同じです。
つまり!
イベントハンドラ内で使う this は、充分気を使え・・・・・・っていうか、使うな! ってことですね。
中国語の「広東語」と「普通語」みたいに、ブラウザで使cchさいけない言葉があるんですね、あるいは、使っても意味が通じない、って感じかな?w
by One-for-you (2010-12-19 00:46)
ブラウザごとに「ある道具」があったりなかったり、共通の道具があってもその「意味」が違ったりします。その違いを吸収してやらないと、環境依存の動作になってしまいます。
by みみちゃん (2010-12-19 11:40)