FLAGS

筆者おかあつ 大きな区分 記事の区分 記事の一覧 検索 ツイート

2013年3月17日日曜日

JavaScriptで変数名のオーバーライドを使ってはならない (oka01-jfhgznhknbdjbeqj)

外側のスコープが持つ変数名を内側のスコープで同じ名前の変数を定義することでオーバーライドする変数名のオーバーライド機能は便利なものだが、JavaScriptではこの機能を使わない方が良さそうだ。何故だろうか。

JavaScriptのvarは、他の言語にない独特な性質がいくつかある。

1.JavaScriptのvarは、Javaの変数宣言と動作が異なる。
2.関数途中でvar 宣言を行った場合でも、変数が作られるのは関数の先頭。
3.変数が作られるのは関数の先頭だが、その変数が初期化は、宣言した位置で行われる。
4.関数内の関数は、変数が作られる時にまとめて作られて初期化される。
5.JavaScriptには、ブロックスコープがない。

これらの特徴が、予期しない落とし穴を作り出すことがある。

次の様なプログラムを考える。


var msg= prompt( 'やあ元気ですか?(貴方の返事を入力して下さい):',"元気" );
if ( msg != null ) {
    alert( "DBG:あなたの返事:" + msg );
    var msg = prompt( msg+ "ですか…それは良かった!(あなたの返事)" ,"最近あれの調子が悪くて…" );
    alert( "DBG:あなたの返事:" + msg );
} else {
    alert( "どうしてなにもいわないのですか?" );
}

ブロックスコープを持つJavaではこの様にブロックの中で変数を宣言する事は稀ではない。JavaScriptでも同様な書き方が可能で、一見すると正しく動作している様に見える。だがここに落とし穴がある。

public class Test {
    static String hello = "hello";
    public static void main(String[] args) {
        System.out.println( hello ); // "hello"=フィールド参照
        String hello = "HELLO";
        System.out.println( hello ); // "HELLO"= 変数参照
    }
}
Javaではこの様に外側のスコープを内側のスコープがオーバーライドして同じ名前の変数を隠す事を安全に行うことが出来る。だがJavaScriptではそこに制約がある。

var msg= prompt( 'やあ元気ですか?(貴方の返事を入力して下さい):',"元気" );
(function() {
    if ( msg != null ) {
        alert( "DBG:あなたの返事:" + msg );
        var msg = prompt( msg+ "ですか…それは良かった!(あなたの返事)" ,"最近あれの調子が悪くて…" );
        alert( "DBG:あなたの返事:" + msg );
    } else {
        alert( "どうしてなにもいわないのですか?" );
    }
})();
試してみればわかるが、上記プログラムでは msg は常にnullに判定され、常に「どうしてなにもいわないのですが」が表示される。これこそが正にJavaScriptのvarが持つ「変数は関数先頭で作成される」「初期化は宣言場所で行われる」のふたつの性質がつくりだす落とし穴である。

var msg="hello";
console.log( msg ); // hello
(function() {
    console.log( msg ); // null
    var msg = "world";
    console.log( msg ); // world
})();
何重にも重ねたクロージャを組み合わせたプログラムを作ると、うっかり外側のクロージャで宣言した変数の名前が、内側のクロージャで宣言した変数の名前と重複してしまうことがある。すると実行環境上では何のエラーも警告も報告されず、予期しない動作をする。この問題は、地味だが検知が非常に難しい。

変数名のオーバーライドを使うと、あるスニペットをコピーペーストで他の部分で流用する時に色々と便利な場合が多い。よって出来るだけ名前を共通にする様にコーディングする場合が多いのではないか。だがJavaScriptでは上記の特殊な性質がある為、変数名のオーバーライドは出来るだけ避けたほうが良い。