javascriptのハマりどころ:巻き上げ

javascriptはほかの言語と違って結構適当に書いても動いたりする、自由度の高い(?)言語です。
だからといってなんとなーくでコードを書いてると、javascript独自の仕様に引っかかって予想外の挙動を起こしたりします。

今回はPHPとかほかの言語を扱ったことがある人がハマりやすい仕様「巻き上げ」について紹介していこうと思います。

巻き上げとは?

まずは、以下のコードを見てください。

var val = 'sample1';
function viewVal(){
    console.log(val);
}
viewVal();

このコードを実行するとコンソールに sample1 が表示されます。
javascriptは関数の外で宣言されたグローバルスコープの変数や関数を呼び出すことができます。
上記のコードだと、viewVal()関数内で宣言されていない変数valを呼び出しています。
結構便利なんですが、気を付けないと巻き上げが発生してハマります。

巻き上げが発生するコード

では実際に巻き上げを発生させてみましょう。

var val = 'sample1';
function viewVal(){
    console.log(val); //1回目のconsole.log
    var val = 'sample2';
    console.log(val); //2回目のconsole.log
}
viewVal();
console.log(val); //3回目のconsole.log

このコードを実行するとどうなるでしょうか?

  • 1回目のconsole.log:sample1
  • 2回目のconsole.log:sample2
  • 3回目のconsole.log:sample1

もしくは

  • 1回目のconsole.log:sample1
  • 2回目のconsole.log:sample2
  • 3回目のconsole.log:sample2

と考えた人は巻き上げの罠にはまっています!

実際に実行してみると、

  • 1回目のconsole.log:undefined
  • 2回目のconsole.log:sample2
  • 2回目のconsole.log:sample1

という結果になるんです。

なぜこんなことになるのか?

javascriptは関数内でvarを使用して変数を宣言すると、位置に関係なく「その関数の先頭で宣言された」と判断します。
つまり、

var val = 'sample1';
function viewVal(){
    var val;
    console.log(val); //1回目のconsole.log
    val = 'sample2';
    console.log(val); //2回目のconsole.log
}
viewVal();
console.log(val); //3回目のconsole.log

というコードと同じ処理の流れになるんです。
これを「巻き上げ」といいます。
これを知らないと小一時間ハマったりします。

巻き上げ対策

1回目のconsole.logでsample1を表示させるには、thisを使いましょう。

var val = 'sample1';
function viewVal(){
    console.log(this.val); //1回目のconsole.log
    var val = 'sample2';
    console.log(val); //2回目のconsole.log
}
viewVal();
console.log(val); //3回目のconsole.log

これで

  • 1回目のconsole.log:sample1
  • 2回目のconsole.log:sample2
  • 2回目のconsole.log:sample1

という結果になります。

ほかにもviewVal()に引数として渡す方法があります。

var val = 'sample1';
function viewVal(val){
    console.log(this.val); //1回目のconsole.log
    var val = 'sample2';
    console.log(val); //2回目のconsole.log
}
viewVal(val);
console.log(val); //3回目のconsole.log

まとめ

巻き上げはjavascript特有の仕様なので、気を付けないとハマってしまいます。
基本的に関数内で使用するローカル変数は、関数の先頭で宣言するようにすると巻き上げを回避することができます。