JavaScriptの変数スコープと(軽く)クロージャ。
JavaScriptの変数スコープには2つしかありません。
- グローバルスコープ
- 関数スコープ
サンプルコードで動作を確認していきます。
サンプルコード
Sample01
コード
(function(){ scope = 'Global Scope'; }()); (function(){ console.log('@Sample01'); console.log(scope); }());
実行結果
@Sample01 Global Scope
解説
JavaScriptでは var を付けづに変数を宣言するとグローバル変数として宣言されます。
グローバル変数として宣言された変数は他の関数からも参照できる事がわかります。
Sample02
コード
(function(){ var scope = 'Global Scope'; (function(){ console.log('@Sample02'); console.log(scope); }()); }());
実行結果
@Sample02 Global Scope
解説
このサンプルでは実行される関数の呼び出し元で宣言された変数も参照できる事がわかります。
Sample03
コード
(function(){ var scope = 'Global Scope'; (function(){ console.log('@Sample03-1'); var scope = 'Function Scope'; console.log(scope); }()); (function(){ console.log('@Sample03-2'); console.log(scope); }()); }());
実行結果
@Sample03-1 Function Scope @Sample03-2 Global Scope
解説
このサンプルでは実行される関数の中で、呼び出し元で宣言された変数と同じ名前の変数を宣言したとしても、呼び出し元で宣言された変数とは別の変数を定義していることがわかります。
Sample04
コード
var scope = 'Global Scope'; (function(){ console.log('@Sample04'); console.log(scope); var scope = 'Function Scope'; console.log(scope); }());
実行結果
@Sample04 undefined Function Scope
解説
1回目に scope を出力しているところで 'GlocalScope' と出力されそうですが、 undefiend が出力されています。
これは、関数オブジェクトの中で scope 属性がホイストされているからです。
ホイスト後のコードは下記のようになります。
var scope = 'Global Scope'; (function(){ var scope; console.log('@Sample04'); console.log(scope); scope = 'Function Scope'; console.log(scope); }());
Sample05
コード
(function(){ console.log('@Sample05'); for(var i=0; i<3; i++){ console.log('[In for]i is ' + i); } console.log('[Out for]i is ' + i); }());
実行結果
@Sample05 [In for]i is 0 [In for]i is 1 [In for]i is 2 [Out for]i is 3
解説
JavaScriptではブロックスコープがないためfor文で宣言した変数もfor文の外から参照できることがわかります。
Sample06
コード
function mockButton(){ return { onclick : {} }; } (function(){ console.log('@Sample06'); var buttons = [mockButton(),mockButton(),mockButton()]; for(var i=0, l=buttons.length; i<l; i++){ buttons[i].onclick = function(){ console.log('onclick' + i); }; } for(var j=0, l=buttons.length; j<l; j++){ buttons[j].onclick(); } }());
実行結果
@Sample06 onclick3 onclick3 onclick3
解説
onclick0 onclick1 onclick2 と出力されることを期待しましたが onclick3 が3回出力されてしまいました。
2つ目のfor文のところでも i は有効なため、 i の最後に設定された値 3 が出力されています。
これを回避するためにクロージャを使い実装します。Sample07です。
Sample07
コード
function mockButton(){ return { onclick : {} }; } (function(){ console.log('@Sample07'); var buttons = [mockButton(),mockButton(),mockButton()]; for(var i=0, l=buttons.length; i<l; i++){ (function(i){ buttons[i].onclick = function(){ console.log('onclick' + i); }; }(i)); } for(var j=0, l=buttons.length; j<l; j++){ buttons[j].onclick(); } }());
実行結果
@Sample07 onclick0 onclick1 onclick2
解説
期待通りの動きになりました。
onclickに関数を設定している無名関数で外部より参照されない i を受け取っているのでonclickに関数を設定している時点で i が定義されています。
Sample08
コード
function mockButton(){ return { onclick : {} }; } (function(){ console.log('@Sample08'); var buttons = [mockButton(),mockButton(),mockButton()]; for(var i=0, l=buttons.length; i<l; i++){ (function(){ buttons[i].onclick = function(){ console.log('onclick' + i); }; }(i)); } for(var j=0, l=buttons.length; j<l; j++){ buttons[j].onclick(); } }());
実行結果
@Sample08 onclick3 onclick3 onclick3
解説
冗長な解説になってしまいますが、このようにonclickに関数を i を引数として与えないとonclick関数が外部にある i を参照してしまい、期待した結果になりません。
Sample08
コード
function mockButton(){ return { onclick : {} }; } (function(){ console.log('@Sample08'); var buttons = [mockButton(),mockButton(),mockButton()]; for(var i=0, l=buttons.length; i<l; i++){ (function(){ buttons[i].onclick = function(){ console.log('onclick' + i); }; }(i)); } for(var j=0, l=buttons.length; j<l; j++){ buttons[j].onclick(); } }());
実行結果
@Sample08 onclick3 onclick3 onclick3
解説
冗長な解説になってしまいますが、このようにonclickに関数を i を引数として与えないとonclick関数が外部にある i を参照してしまい、期待した結果になりません。
サンプルコード(補足)
上記の内容で十分説明できているので下記のコードの紹介は不要だと思いますが、混乱しやすい内容なので記載しておきます。
Sample09
コード
function mockButton(){ return { onclick : {} }; } (function(){ console.log('@Sample09'); var buttons = [mockButton(),mockButton(),mockButton()]; for(var i=0, l=buttons.length; i<l; i++){ buttons[i].onclick = function(){ console.log('onclick' + i); }; } for(var i=0, l=buttons.length; i<l; i++){ buttons[i].onclick(); } }());
実行結果
@Sample09 onclick0 onclick1 onclick2
解説
期待した通りに動いているように見えますが、これは2つ目のfor文で i を上書きしているのでそのように見えるだけです。
Sample10
コード
function mockButton(){ return { onclick : {} }; } (function(){ console.log('@Sample10'); var buttons = [mockButton(),mockButton(),mockButton()]; function clickAll(){ for(var i=0, l = buttons.length; i<l; i++){ buttons[i].onclick(); } }; for(var i=0, l = buttons.length; i < l; i++){ buttons[i].onclick = function(){ console.log('onclick' + i); }; } clickAll(); }());
実行結果
@Sample10 onclick3 onclick3 onclick3
解説
このサンプルでは clickAll関数 で定義した i が呼び出し元の i と別の変数として定義され、外側の変数に影響を与えていないことがわかります。
最後に。
以上です。
指摘等あればコメント頂けると幸いです。
参考
書籍
- 作者: Christian Johansen,長尾高弘
- 出版社/メーカー: アスキー・メディアワークス
- 発売日: 2011/11/25
- メディア: 大型本
- 購入: 19人 クリック: 331回
- この商品を含むブログを見る