JavaScriptの関数内で宣言された変数は関数全体で有効
という挙動が直感的じゃなくて時々ハマるのでメモ。
JavaScriptの関数では変数宣言はどこでもできて、どこで宣言しても関数の頭から有効になるという特徴を持っている。
JavaScriptはブロックスコープを持たないという事柄と一緒に紹介されることが多い。
挙動を単純なサンプルコードで検証してみる
宣言されてない変数を使おうとするとエラーになるが、宣言されていれば内容が未定義でもエラーにはならない。
という原則を踏まえて変数宣言の挙動を見てみる。
ちなみに直接関係ないがNode.jsのコンソールがあるとこういうちょっとした検証に便利だ。
宣言されてないのでエラーになる
f = function(){ return y; } f();
未定義だが宣言されているのでエラーにはならない
f = function(){ return x; var x = 1; } f();
上のコードと等価のコード
f = function(){ var x; return x; x = 1; } f();
バグの引き金になる事例
var y = 1; f = function(){ y = y * 2; var y = y * 2; return y; } f();
これを実行すると4ではなくNaNが返る。
クロージャで関数外部の変数にアクセスしているつもりが、関数内部の変数を未定義のまま使ってしまうと言う状況。
4行目のvarで関数内のyが宣言されているため、同じ関数内ではそちらのyが使われる。(JavaScriptのスコープチェーンを知っていると理由が理解できる)
例が簡単なので、いくらなんでもこんなコードだれも書かないだろうが、それぞれの行の間に別の複雑な処理が挟まっていたとしたら?
うっかり同じ名前の変数を定義してしまい、罠にはまることは誰にでもありそうだ。
しかもこの変数宣言の特徴を知っていればまだすぐに原因がわかるのでいいが、知らない場合は解決のためにとてつもなく長い旅に出る羽目になるかもしれない。
羽目になりましたとも。ごく最近に。
変数宣言のベストプラクティス
混乱を避けるために先頭でまとめて宣言するというのがジャスティス。
他の多くの言語だと使う直前に宣言するのが一般的だけど、JavaScriptではそれは非推奨。
参考
JavaSciprt: The Good Parts — 「良いパーツ」によるベストプラクティス
実践的な要点がまとまった非常にいい本。
他の言語の経験のある開発者がJavaScriptに手を出す際には是非の是非にオススメ。
電子書籍にもなってる。
ここのところはまりどころにつまずいては、確かこの本に書いてあったな、と思い出してページをめくり直す日々です。