スポンサーリンク

JavaScriptの動かないコード (中級編) イベントの詳細情報を第一引数で取得できない

以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)


何かキーを押すと,キーコードを表示します。


<script language="JavaScript">

// キー押下時のイベントリスナを設定
document.onkeydown = f;

function f( e )
{
	// キーコードを表示
	alert( "キーコードは" + e.keyCode );
}

</script>




答え


Firefoxでは,押したキーのキーコードが表示される。

しかしIEでは,

ランタイムエラーが発生しました。
デバッグしますか?

エラー:'keyCode' は Null またはオブジェクトではありません。

のようなエラーダイアログが表示される。

IEでは, e が定義されていない。



このようになる原因を調べるにあたり,イベント設定方法を3種類取り上げ,それぞれの方法について「動くコード」と「動かないコード」を比較してみよう。


(1)イベントハンドラプロパティに代入する場合

まず冒頭のコードから。

何かキーを押すと,キーコードを表示します。


<script language="JavaScript">

document.onkeydown = f;
	// FFでは,代入したイベントリスナの第一引数に
	//   イベントオブジェクトが渡る。
	// IEでは,第一引数には何も渡されない。

function f( e )
{
	// FFでは e はイベントオブジェクト
	// IEでは e は未定義

	// IE:NG, FF:OK
	alert( "キーコードは" + e.keyCode );
}

</script>

ソース中のコメントにある通り,FFではイベントリスナの第一引数にイベントオブジェクトが渡るので,イベントリスナ中でそれを解析できる。

Mozillaのサイト
https://developer.mozilla.org/Ja/Core...


イベントハンドラとしての関数


JavaScript では、DOM イベントハンドラは関数です (他の DOM 言語バインディングでは handleEvent メソッドを持つオブジェクトであるのに対して) 。


その関数には、第一番目かつ唯一のパラメータとしてイベントオブジェクトが渡されます。


他のパラメータと同じように、イベントオブジェクトを使う必要が無ければ仮引数のリストから省略できます。


でもIEでは,第一引数に何も入ってこない。



かわりに,IEではグローバルの変数として "event" があるので,これを使えばIEでもイベント情報を調査できる。

何かキーを押すと,キーコードを表示します。


<script language="JavaScript">

document.onkeydown = f;

function f( e ) // FFでは e はイベントオブジェクト
{
	// もし第一引数の e が未定義なら,代用品として window.event を使う。
	e = e || window.event;

	// IE:OK, FF:OK
	alert( "キーコードは" + e.keyCode );
}

</script>

これで両方動く。

Firefoxでの擬似window.event
http://p2b.jp/index.php?UID=1149066600


Gecko系のブラウザでは、これはNetscape4.x時代からずっとそうなんだけど、イベントは、イベントハンドラーにargumentとして渡されるので、この手の記述には必ず引数(ひきすう)を書く必要がある。


一方、IEでは(Operaもか?)、イベントモデルには、最後に生じたイベントをキャプチャーするwindow.eventという属性があり、これはどこからでも自由にアクセスすることが出来る。


もしくは,関数の定義側(シグネチャ)に引数を明示しなくても,arguments オブジェクトを使って引数を取得してもよい。

何かキーを押すと,キーコードを表示します。


<script language="JavaScript">

document.onkeydown = f;

function f()
{
	// 第一引数か window.event のどちらか
	evt = arguments[0] || window.event;

	// IE:OK, FF:OK
	alert( "キーコードは" + evt.keyCode );
}

</script>


argumentsには,関数呼び出し時の引数などが格納されている。

firefoxとjavascriptとeventと
http://oshiete1.goo.ne.jp/qa3881325.h...


■object.onclick = function(){ }


こういう書き方をスクリプト内でする場合は、Firefoxでは関数の引数0に自動的にイベントオブジェクトが入るので
arguments[0] がイベントオブジェクトになります。


IEでは window.eventでとる必要があります。


Opera、Safariは arguments[0] も window.eventも理解できるはずです。

ただし,下記の例はだめ。

何かキーを押すと,キーコードを表示します。


<script language="JavaScript">

function f(e)
{
	// FF:NG
	alert( "キーコードは" + e.keyCode );
}

document.onkeydown = function(){ f() };

</script>

onkeydown属性に関数を代入する際,function() の部分で () の中身が空なので,実行の時点で「引数は渡さない」と明示的に主張していることになる。


なので,第一引数にイベントオブジェクトが渡らない。


(2)bodyタグに属性を埋め込む場合

HTML中にイベントハンドラを記述する場合。前項の復習から:

<body onKeydown="f()">

何かキーを押すと,キーコードを表示します。


<script language="JavaScript">

function f()
{
	// IE:OK, FF:NG
	alert( "キーコードは" + window.event.keyCode );
		// IEには event というグローバルオブジェクトがある。
		// FFには無い
}

</script>

前項で述べた通り,IEではグローバルの event オブジェクトを利用できるが,FFにはない。


かわりに,「第一引数に渡ってくるイベントオブジェクト」を利用すればよいのだった。



でもどうやって第一引数に受け取るのか・・・?

	body onKeydown="f()"

と書いてしまった時点で,() の中身の引数は実行時に必ず空であり,何も引数を渡す事はできないのだ。

渡さないので,arguments[0] で取得する事もできない。


そこを無理やり

<body onkeydown="f(e)">

何かキーを押すと,キーコードを表示します。


<script language="JavaScript">

function f(e)
{
	alert( "キーコードは" + e.keyCode );
}

</script>

のような事をして何となく引数を入れてみても,「e is not defined」とfirebugに冷たく突き放されるだけ。


引数として受け取りたいのに,受け取る手段がない。さあ困った。




と思いきや,Firefoxでは,「タグ属性の引数中」でのみ,eventという名前のイベントオブジェクトを参照できる。

<body onkeydown="f(event)">

何かキーを押すと,キーコードを表示します。

<script language="JavaScript">

function f( e )
{
	// IE:OK, FF:OK
	alert( e.keyCode );
}

</script>

</body>

これならIE・FF共に正常に動作する。


ただし,onkeydown="f(event)" の「event」は

  • IEでは,グローバルのイベントオブジェクト名
  • FFでは,タグ属性の関数の引数中でのみ特例として参照できるイベントオブジェクト名


のように差がある。(スコープが異なる)



参考:

Netscapeのころの挙動
http://www.graviness.com/virgo/javasc...


イベントハンドラをHTML属性として定義した場合、Netscapeのイベントオブジェクトは何になるでしょうか?


答えは event です。


この場合、Netscapeではブラウザ側で引数が用意され、その引数名は event になっています。



メモ──Javascriptにおけるイベントの取得
http://hkom.blog1.fc2.com/blog-entry-...

つまり、呼び出す側の、HTML文の属性に記述されたイベントハンドラーの引数に function(event) のようにevent キーワードを記述する必要があるのである。

(3)イベント追加用の関数を使う場合

addEventを経由する場合。


何かキーを押すと,キーコードを表示します。


<script language="JavaScript">

addEvent( document, "keydown", f );

function f( e )
{
	// IE:OK, FF:OK
	alert( e.keyCode );
}

// イベント追加用の関数
function addEvent( element, eventName, func ){  
	if ( element.addEventListener )
	{
		// Firefox
		element.addEventListener( eventName, func, false );
	}
	else if ( element.attachEvent )
	{
		// IE
		element.attachEvent( "on" + eventName, func );  
	}
}

</script>

これは両方正常に動く。


つまり,さっきまでは

IEではイベントリスナの第一引数にイベントオブジェクトが渡らないから,IEではわざわざ event というグローバル変数を使わないといけない!

と言っていたのに,attachEventを使った場合は,IEでも第一引数にイベントオブジェクトが渡るのだ。

まとめ

上記は「イベント設定方法別」に記述した。


下記では,「イベントオブジェクトの参照方法別」にまとめてみる。


2つのやり方がある。

(1)イベントリスナの第一引数から,イベントオブジェクトを参照できるか?

attachEventした場合
  IE:OK, FF:OK


タグ属性で "=f()"
  IE:NG, FF:NG


タグ属性で "=f(event)"
  IE:NG, FF:OK


イベントハンドラに代入時, () を付けない( = f; )
  IE:NG, FF:OK


イベントハンドラに代入時, () を付ける( = function(){ f(); }; )
  IE:NG, FF:NG

(2)event という名の変数から,イベントオブジェクトを参照できるか?
	IE:グローバルなのでいつでも可

	FF:「タグ属性の関数の引数中」でのみ

(結論)クロスブラウザで推奨される記法

  • 小規模なコードの場合,"=f(event)" としてタグに埋め込む。引数の「event」を抜かさずに。
  • 通常規模の場合,addEvent を使う。

いずれの場合もイベントリスナの第一引数がイベントオブジェクトとなる。

第一引数に注目するのが面倒だったら,イベントリスナ中で arguments[0] || window.event で取得する。




これだけ見ると,何とも複雑な仕様。