HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

イベントハンドラからのイベントオブジェクトの参照

あるところ(何)で、次のようなイベントハンドラが定義されていて、Firefoxでエラーになるという状況に遭遇したらしい。

document.onkeydown = function() {
    if (event.keyCode == 32) {
        ...
    }
    ...
};
エラー:event is not defined

まぁ、確かにコードを見る限りでは、'event'という変数はどこにも定義されていないので、言い分はごもっとも。
問題は、これがたとえばSafariでは動作しているということ。
そこで、ちょっと調べてみた。

調査対象と調査用コード

調査したブラウザのUser-Agentは次のとおり。

使用したコードは次のとおり。

<html>
    <head>
        <script type="text/javascript" src="event-handler.js"></script>
        <script type="text/javascript" src="user-agent.php"></script>
        <script type="text/javascript">
            document.onkeydown = defineFunction('document.onkeydown  ', false);
            document.onkeyup = defineFunction('document.onkeyup    ', true);
        </script>
    </head>
    <body>
        <pre id="msg"></pre>
        <pre id="user-agent"></pre>
    </body>
</html>

event-handler.js。

function log(msg) {
    var elem = document.getElementById('msg');
    elem.appendChild(document.createTextNode(msg));
    elem.appendChild(document.createElement('br'));
}
function defineFunction(description, withArg) {
    var desc = description;
    if (withArg) {
        return function(ev) {
            log(desc + ': ' + 'arguments.length: ' + arguments.length);
            for (var i = 0; i < arguments.length; ++i) {
                try {
                    log(desc + ': ' + '    arguments[' + i + ']' + ': ' + arguments[i]);
                } catch (e) {
                    log(desc + ': ' + '    arguments[' + i + ']: Exception: ' + e.message);
                }
            }
            try {
                log(desc + ': ' + 'event' + ': ' + event);
            } catch (e) {
                log(desc + ': ' + 'event: Exception: ' + e.message);
            }
            try {
                log(desc + ': ' + 'ev' + ': ' + ev);
            } catch (e) {
                log(desc + ': ' + 'ev: Exception: ' + e.message);
            }
        };
    } else {
        return function() {
            log(desc + ': ' + 'arguments.length: ' + arguments.length);
            for (var i = 0; i < arguments.length; ++i) {
                try {
                    log(desc + ': ' + '    arguments[' + i + ']' + ': ' + arguments[i]);
                } catch (e) {
                    log(desc + ': ' + '    arguments[' + i + ']: Exception: ' + e.message);
                }
            }
            try {
                log(desc + ': ' + 'event' + ': ' + event);
            } catch (e) {
                log(desc + ': ' + 'event: Exception: ' + e.message);
            }
            try {
                log(desc + ': ' + 'ev' + ': ' + ev);
            } catch (e) {
                log(desc + ': ' + 'ev: Exception: ' + e.message);
            }
        };
    }
}

このコードでは、次の2つのパターンについて調べている。

実行結果

Firefox 3.6.10

document.onkeydown  : arguments.length: 1
document.onkeydown  :     arguments[0]: [object KeyboardEvent]
document.onkeydown  : event: Exception: event is not defined
document.onkeydown  : ev: Exception: ev is not defined
document.onkeyup    : arguments.length: 1
document.onkeyup    :     arguments[0]: [object KeyboardEvent]
document.onkeyup    : event: Exception: event is not defined
document.onkeyup    : ev: [object KeyboardEvent]

Safari 5.0.2

document.onkeydown  : arguments.length: 1
document.onkeydown  :     arguments[0]: [object KeyboardEvent]
document.onkeydown  : event: [object KeyboardEvent]
document.onkeydown  : ev: Exception: Can't find variable: ev
document.onkeyup    : arguments.length: 1
document.onkeyup    :     arguments[0]: [object KeyboardEvent]
document.onkeyup    : event: [object KeyboardEvent]
document.onkeyup    : ev: [object KeyboardEvent]

IE 8.0

document.onkeydown  : arguments.length: 0
document.onkeydown  : event: [object]
document.onkeydown  : ev: Exception: 'ev' は宣言されていません。
document.onkeyup    : arguments.length: 0
document.onkeyup    : event: [object]
document.onkeyup    : ev: undefined

Chrome 6.0

document.onkeydown  : arguments.length: 1
document.onkeydown  :     arguments[0]: [object KeyboardEvent]
document.onkeydown  : event: [object KeyboardEvent]
document.onkeydown  : ev: Exception: ev is not defined
document.onkeyup    : arguments.length: 1
document.onkeyup    :     arguments[0]: [object KeyboardEvent]
document.onkeyup    : event: [object KeyboardEvent]
document.onkeyup    : ev: [object KeyboardEvent]

Opera 10.63

document.onkeydown  : arguments.length: 1
document.onkeydown  :     arguments[0]: [object KeyEvent]
document.onkeydown  : event: [object KeyEvent]
document.onkeydown  : ev: Exception: Undefined variable: ev
document.onkeyup    : arguments.length: 1
document.onkeyup    :     arguments[0]: [object KeyEvent]
document.onkeyup    : event: [object KeyEvent]
document.onkeyup    : ev: [object KeyEvent]

Sleipnir 2.9.4

document.onkeydown  : arguments.length: 0
document.onkeydown  : event: [object]
document.onkeydown  : ev: Exception: 'ev' は宣言されていません。
document.onkeyup    : arguments.length: 0
document.onkeyup    : event: [object]
document.onkeyup    : ev: undefined

結果を見ると分かるように、Firefox以外は'event'という、コード上は宣言されていない変数でイベントオブジェクトを参照できることが分かる(IE 8.0やSleipnir 2.9.4は分かりにくいが、これもイベントオブジェクトになっている)。
じゃあ、適当な名前の変数名を引数として宣言すればいいかというと、'ev'という変数を引数として宣言しているにもかかわらず、IE 8.0とSleipnir 2.9.4ではイベントオブジェクトが渡されていない。
また、試してみると分かるが、'event'という変数を引数として宣言してしまうと、IE 8.0およびSleipnir 2.9.4において、'event: undefined'となってしまう。つまり、どこかに宣言されている、イベントオブジェクトを指す'event'という変数が、引数に同名の変数を宣言することによって隠蔽されてしまうらしい。
結局、次のようなコードを書かないと、今回試したすべてのブラウザで共通のコードを使用できない、と。

document.onkeydown = function(ev) {
    if (!ev) {
        // イベントオブジェクトが引数に渡されるブラウザでは、ここを通らない
        ev = event;
    }
    if (ev.keyCode == 32) {
        ...
    }
    ...
};

で、はてな記法(JavaScript)でカラーリングしてみると分かるが、'event'はやはり特別扱いされるもののようだ、と気づく。この件に関しては、Firefoxが異端なのか‥
まぁ、今の世の中、ライブラリが山ほど公開されているので、こういう低レイヤーのコードを書いて苦しむことも少ないのだろうけど。知識として知っておくことは損ではないと思う。