JavaScript Diary

Eventオブジェクトの取得 [ 2001/12/23 ]

Netscape と InternetExplorer ではイベントモデルに互換性がないのはよく知られていることです。
また、イベントモデルもそうですが IE と NS ではイベントの格納される場所も違います。

IE の場合、イベントオブジェクトは常に「グローバルオブジェクト(event)」であるのに対し、NSでは「イベントハンドラの第一引数」がイベントオブジェクトになります。

つまり、クロスブラウザなイベントオブジェクトの取得方法は次のようになります。

function handler( e ){
    if( document.all !== void 0 ) e = window.event ; // IE の場合、グローバルオブジェクトから取得
    alert( e );
}

document.onclick = handler ;

これは特に珍しくもなく、極々普通に行われていることです。
この e を使いイベントの発生した座標などを取得することになります。

さて本題に入る前にちょっと余談ですが(以外に知らない人もいると思うので)、以下のようにイベントハンドラをHTML属性として定義した場合、Netscapeのイベントオブジェクトは何になるでしょうか?

<a href="#" onClick="return false;"> test </a>

答えは event です。
この場合、Netscapeではブラウザ側で引数が用意され、その引数名は event になっています
つまり、この場合だけ以下のように分岐することなく以下のように書けます。

<a href="#" onClick="alert( event );return false;"> test </a>

これは以下のように書いたのと同じです。

document.links[〜].onclick = function( event ){
    if( document.all !== void 0 ) event = window.event ;
    alert( event );
    return false ;
};

さて、本題に戻ります。
ここで、このイベントオブジェクトを取得するクロスブラウザ関数 getEvent関数 を定義したいと思います。

function getEvent( e ){
    if( document.all !== void 0 ){
        return window.event ;
    }else{
        return e ;
    }
}
使用例は先ほどの例を用いると、
function handler( evt ){
    var e = getEvent( evt );
    alert( e );
}

document.onclick = handler ;

余計、うざったくなってしまいましたね ^^); 結論はこれではありません。
Netscapeではイベントハンドラの第一引数は必ずイベントオブジェクトですので、次のようにも書けます。

function handler(){
    var e = getEvent( arguments[0] );
    alert( e );
}

さて、勘の良い人は気付いたかも知れませんね。
今回やりたいことはこの evt やら arguments[0] やらの引数自体をなくすことです。
つまり、以下のように書きたいわけです。

function handler(){
    var e = getEvent();
    alert( e );
}

不可能なように思いますが、[ 2001/06/15 ] Netscape(4.x) の関数は特殊 の後半にも書いてますが、この呼び出し元の関数を走査することにより、そして argumentsオブジェクトの第一引数を調べることにより可能です。

getEvent関数 は以下のようになります。

// Event getEvent( void );
function getEvent(){
    if( document.all !== void 0 ){ // IE の場合、グローバルイベントオブジェクトを返す
        return window.event ;
    }else{
        // 呼び出し元関数を遡る
        // ループの終了は f が null になったとき
        for(var f=arguments.callee.caller;f!=null;f=f.caller){
            // 第一引数を参照する
            var e = f.arguments[0];
            // イベントオブジェクトである場合、それを返す
            if( e !== void 0 && typeof e == 'object' && e.toString() == '[object Event]' ) return e ;
            // NN4 ではバグがあるため(nullにならない場合がある)、自分自身を参照した場合ループを終了する
            if( f.caller == f ) break ;
        }
        return null ;
    }
}

この関数の使用例はこちら(IEで確認しても意味ないです :->)
まだ、使用例以外でテストしていませんが、それなりに動作すると思います。

はぅ、ところで呼び出し元関数を参照するとき arguments.caller.calleearguments.callee.caller はECMA仕様としてどっちが正しいのでしょうか?
僕の今までの認識では callee はfunction型、caller は Argument型と認識していましたが・・・何か複雑です。

2002/03/21追記: Opera6.01ではArgumentオブジェクトの callee, callerプロパティが存在しないためエラーが発生します。大人しく第一引数から参照するしかないようです。