イベントハンドラ (event handler)

DOMイベント

分類 イベントハンドラ 発生タイミング
ウィンドウ onresize ウィンドウのサイズが変更されたとき
(Windowオブジェクトのみ)
  onscroll 要素のコンテンツがスクロールされたとき
ドキュメント onload リソースのロードが完了したとき
onunload アンロードされるとき (ロードする前)
readystatechange documentのreadyState属性が変化したとき
フォーム onsubmit ※1 フォームの送信が要求されたとき
onreset フォームのリセットが要求されたとき
onchange ※1 要素の値が変更され、要素が入力フォーカスを失ったとき
onselect テキストが選択されたとき
onfocus ※1 要素が入力フォーカスを得たとき
onblur ※1 要素が入力フォーカスを失ったとき
oninput inputまたはtextarea要素のValueが変化したとき
マウス onclick マウスでクリックされたとき
ondblclick マウスでダブルクリックされたとき
onmousedown マウスボタンが押されたとき
onmouseup マウスボタンが離されたとき
onmouseover マウスポインタが要素に乗ったとき
onmouseout マウスポインタが要素から離れたとき
onmousemove マウスポインタが要素の上を移動したとき
oncontextmenu マウスで右クリック、またはアプリケーションキーが押されるなどして、コンテキストメニューが表示されるとき
キー onkeydown ユーザーがキーを押したとき
onkeypress ユーザーがキーを押して離したとき
onkeyup ユーザーがキーを離したとき
リソース onabort リソースのロードが中断されたとき
onerror リソースのロード中にエラーが発生したとき
※1 プログラムから変更したときは、これらのイベントは発生しない

onloadやonclickなど、イベントハンドラはすべて小文字で記述します。

要素で発生するイベントやそのタイミングは、FirebugのコマンドラインAPIであるmonitorEvents()で確認できます。

イベントハンドラの登録

登録されたイベントハンドラは、JavaScript デバッガのイベントペインで確認できます。

HTMLの属性

HTMLの要素の属性として記述します。

<a onclick="FunctionName();"></a>

属性値はJavaScriptのコードであり、関数名ではありません。よって関数名の後にかっこが必要となります。

DOMレベル0

JavaScriptで要素のプロパティとして記述します。DOMレベル0では、オブジェクトの1つのイベントに対して、1つのハンドラしか登録できません。同一のイベントに後から登録すると、先の登録が上書きされます。

element.onclick = Function1;
element.onclick = Function2; // Function1が上書きされる

たとえば次のように、1つのa要素に2つのハンドラを登録すると、

var a = document.createElement( 'a' );

a.onclick = function() { alert( '1' ) };
a.onclick = function() { alert( '2' ) };

この要素をクリックしたときには、'2'と表示されるダイアログだけが表示されます。

また、DOM0レベルではイベントフェーズの指定ができず、DOM階層の上位にある要素のハンドラが呼び出されることはありません。

ハンドラの削除

ハンドラを削除するには、nullを代入します。

element.onclick = null;

DOMレベル2 (イベントリスナ)

target.addEventListener( type, listener [, useCapture ] );
EventTarget.addEventListener - DOM | MDN

typeでイベント名を指定します。これは「click」などのイベントの名前であり、「onclick」のようなイベントハンドラの名前ではありません。この文字列は大文字/小文字の区別はされません。

listenerには、イベントを処理する関数、もしくはhandleEvent()メソッドを実装するオブジェクトを渡します。

useCaptureでは、listenerが呼び出されるイベントフローのフェーズを指定します。

Internet Explorer 9より前はuseCaptureは省略不可であり、省略した場合には例外が発生します。よって互換性を考慮するならば、既定値である場合にも省略をせずfalseと明示します。ブラウザ実装状況 - EventTarget.addEventListener - Web API インターフェイス | MDN

複数のハンドラの登録

DOMレベル2では、同一オブジェクトの同一イベントに対して、複数のハンドラを登録できます。たとえば次のように、1つのa要素に2つのハンドラを登録すると、

var a = document.createElement( 'a' );

a.addEventListener( 'click', function() { alert( '1' ) }, false );
a.addEventListener( 'click', function() { alert( '2' ) }, false );

この要素をクリックしたときには、'1'と'2'の2つのダイアログが表示されます。

ただし同一イベントに同一のハンドラを重複して登録すると、後の登録は無視されます。よって次のようにすると、

var a = document.createElement( 'a' );
var foo = function() { alert( 'Hello' ) }

a.addEventListener( 'click', foo, false );
a.addEventListener( 'click', foo, false );

要素のクリックでは、ダイアログが1度だけ表示されます。複数の同一のイベントリスナー - EventTarget.addEventListener - Web API インターフェイス | MDN

イベントハンドラはイベントを捕捉し処理する関数であり、イベントリスナはそのハンドラ関数を処理する仕組みです。ただし文脈によっては区別されません。 event handler - Document Object Model (DOM) Level 3 Events Specification

すべてのイベントを、1つのメソッドで受け取る

addEventListener()の第2引数にオブジェクトを渡すと、イベント発生時にはそのオブジェクトのhandleEvent()メソッドが呼び出されます。

var obj = {
    handleEvent: function( event )
    {
    }
};

window.addEventListener( 'load', obj, false );
// 'load'イベント発生時、obj.handleEvent()が呼び出される。

複数のイベントを登録したときには、handleEvent()の引数のEvent.typeプロパティから、イベントの種類を判別できます。

function handleEvent( event )
{
    switch( event.type )
    {
        case 'load':
            break;

        case 'unload':
            break;
    }
}

window.addEventListener( 'load', this, false );
window.addEventListener( 'unload', this, false );

この例ではハンドラとしてthisを渡しています。これはメソッド外ではthisはグローバルオブジェクトへの参照であるためで、これにより同じスコープにあるhandleEvent()が呼ばれます。

上書きされる恐れがあるため、実際にはhandleEvent()をグローバルスコープで定義すべきではありません

ハンドラの削除

ハンドラを削除するには、removeEventListener()を呼びます。

element.removeEventListener( type, listener[, useCapture] );
EventTarget.removeEventListener - Web API Interfaces | MDN

typeはハンドラ登録時にaddEventListener()で指定したイベントと同名で、さらにlistener登録時の関数と同一でなければなりません。よって、たとえば次のようにしてもハンドラを削除できません。

function Foo( flag )
{
    function EventHandler()
    {
        alert( '' );
    }

    if( flag )
    {
        button.addEventListener( 'click', EventHandler, false );
    }
    else
    {
        button.removeEventListener( 'click', EventHandler, false );
    }
}

Foo( true );  // ハンドラを登録する
Foo( false ); // ハンドラを削除する

これはハンドラとして登録される関数EventHandler()がローカルスコープで定義されているため、2度目の関数呼び出し時には異なるオブジェクトとなるためです。

ところでハンドラ内でそのイベントを削除するならば、イベントのプロパティを参照する方法が使えます。この方法ならば、ハンドラが匿名関数であっても削除できます。

Internet Explorerのイベントモデル

Internet Explorer 9より前はaddEventListener()をサポートしておらず、attachEvent()がその代替となります。attachEvent method (Internet Explorer) | MSDN

element.attachEvent( 'onclick', Function1 );
※ IEのイベントモデルでは、イベントフローの呼び出しフェーズの制御がサポートされません。

attachEvent()ではハンドラ内でのthisの値が、addEventListener()と異なります。

attachEvent()は、Internet Explorer 11以降ではサポートされませんCompatibility changes in IE11 (Windows) | MSDN

ハンドラの削除

ハンドラを削除するには、detachEvent()を呼びます。

element.detachEvent( 'onclick', Function1 );

クロスブラウザに対応したイベントの実装

クロスブラウザとするために、各メソッドが実装されているか確認しながら順に試行します。ブラウザ実装状況 - EventTarget.addEventListener - Web API インターフェイス | MDN

イベントハンドラの登録

var element = document.createElement( 'a' );
if( element.addEventListener )
{
    // DOMレベル2
    element.addEventListener( 'click', FunctionName, false );
}
else if( element.attachEvent )
{
    // IEイベントモデル
    element.attachEvent( 'onclick', FunctionName );
}
else
{
    // DOMレベル0
    element.onclick = FunctionName;
}

より汎用的には、

function AddEventListener( element, type, listener )
{
    if( element.addEventListener )
    {
        element.addEventListener( type, listener, false );
    }
    else if( element.attachEvent )
    {
        element.attachEvent( 'on' + type,
            function() { listener.apply( element, arguments ); } );
    }
    else
    {
        // not support.
    }
}

のように関数化します。

イベントハンドラの削除

var element = document.createElement( 'a' );
if( element.removeEventListener )
{
    // DOMレベル2
    element.removeEventListener( 'click', FunctionName, false );
}
else if( element.detachEvent )
{
    // IEイベントモデル
    element.detachEvent( 'onclick', FunctionName );
}
else
{
    // DOMレベル0
    element.onclick = null;
}

イベントハンドラの呼び出し

DOMレベル0

イベントハンドラのプロパティを関数のように実行することで、イベントハンドラを明示的に呼び出せます。たとえばonsubmitイベントハンドラは、onsubmit()として呼び出せます。

// イベントハンドラの登録
foo.onsubmit = function() {}

// イベントハンドラの呼び出し
foo.onsubmit();

Form.submit()の呼び出しではsubmitイベントは発生しないため、イベントハンドラは呼び出されません。

イベントハンドラが匿名関数でなければ、当然その関数名で呼び出せます。

function Bar() {}

// イベントハンドラの登録
foo.onsubmit = Bar;

// 関数の呼び出し
Bar();

DOMレベル2

addEventListener()で登録された関数を、後から参照する方法はありません。よってハンドラを呼ぶ必要があるならば、その関数は名前を付けて定義するようにします。そうすれば、その関数名で呼び出すことが可能です。

function Bar() {}

// イベントハンドラの登録
foo.addEventListener( 'click', Bar, false );

// 関数の呼び出し
Bar();

ただし、

の3つのイベントは、それを発生させるメソッドが用意されています。よって、これらのメソッドの効果がある要素ならば、メソッドを呼ぶことで対応するイベントハンドラを呼び出せます。

foo.addEventListener( 'click', function() {}, false );
foo.click();

thisキーワード

イベントハンドラ内でthisキーワードが参照する値は、イベントモデルによって異なります。

イベントモデル 参照するオブジェクト
DOMレベル0 ハンドラが登録されたオブジェクト
DOMレベル2 未定義 (一般的に、ハンドラが登録されたオブジェクト)
IEイベントモデル Windowオブジェクト

DOMレベル2とIEイベントモデルで、汎用的にハンドラが登録されたオブジェクトを参照するには、EventオブジェクトのcurrentTargetプロパティを使用します。

function EventHandler( event )
{
    var e = event || window.event;
    var obj = e.currentTarget;
}

thisキーワードの取得例

HTMLの属性

<a onclick="alert( Object.prototype.toString.call( this ) );"
  href="javascript:;">Click me!</a>

DOMレベル0

イベントハンドラ内での、thisの型名を表示します。

a要素 Click me!
input要素

ハンドラが登録されたオブジェクトをthisが参照するのは、ハンドラ内だけです。そのハンドラ内から呼ばれた関数内では、グローバルオブジェクトを参照します。

var input = document.createElement( 'input' );

input.onclick   = Foo;                 // object HTMLInputElement
input.onkeydown = function() { Foo() } // object Window (ハンドラ内からの関数の呼び出し)

document.body.appendChild( input );


function Foo()
{
    alert( Object.prototype.toString.call( this ) );
}

つねにthisが同じオブジェクトを参照するようにするには、イベントをhandleEvent()で受け取るようにします。

スコープ

イベントハンドラのスコープを調べるため、3つのボタンにonclickイベントを設定し、それをクリックしたときの動作を検証します。

関数内で変数の値を変更しない場合

function Foo( x )
{
    var button = document.createElement( 'input' );
    button.type = 'button';
    button.value = x;
    button.onclick = function()
    {
        alert( x );
    }

    document.body.appendChild( button );
}

Foo( 0 );
Foo( 1 );
Foo( 2 );

変数xのスコープは関数Foo()の中にあり、関数内では値を変更されません。このとき3つのボタンをクリックすると、それぞれ0、1、2と引数の値をそのまま表示します。

関数内で変数の値を変更する場合

function Bar()
{
    for( var x = 0; x < 3; x++ )
    {
        var button = document.createElement( 'input' );
        button.type = 'button';
        button.value = x;
        button.onclick = function()
        {
            alert( x );
        }

        document.body.appendChild( button );
    }
}

Bar();

変数xのスコープは関数Bar()の中にあり、forループで0、1、2、3の値をとります。このとき3つのボタンをクリックすると、いずれも3と表示します。これはこの関数のスコープを外れた時点での、変数xの値です。

変数のスコープ

HTML属性として定義されたイベントハンドラのスコープ

たとえば、

<input type="button" onclick="alert( 'OK' );" />

のようにHTML属性として定義されたイベントは、通常の関数とは異なるスコープを持ちます。