JavaScriptの変数とスコープ

/web/javascript

Note: この記事は、3年以上前に書かれています。Webの進化は速い!情報の正確性は自己責任で判断してください。

一度しっかり試しておきたかった変数とその有効範囲について。

  • JavaScriptには変数の型という考え方がない(任意のデータ型の値を代入できる)
  • JavaScriptでサポートされるデータ型には、基本型と参照型がある
  • 参照型の変数には、参照が格納される(実際のデータ値ではない)
  • 関数、配列、オブジェクトなどが参照型
  • 変数の有効範囲を、その変数のスコープという
  • 暗黙的に宣言された変数は、必ずグローバル変数になる
  • 関数の内部で暗黙的に宣言された場合でも、グローバル変数になる
  • var文で宣言した変数は、ローカル変数になる
  • ローカル変数のスコープは、その変数が宣言された関数の中だけに限定される
  • 関数の中にグローバル変数と同じ名前のローカル変数があった場合は、ローカル変数が優先される
  • ある関数で宣言された変数は、「関数全体」で有効(値はともかく、宣言自体に順序は関係ない)
  • 関数の引数もローカル変数の仲間
  1. var a = 3.14;
  2. var b = a;
  3. a = 4;
  4. alert(b); // 3.14 が出力される
  5.  
  6. var c = [0,1,2];
  7. var d = c;
  8. c[0] = 99;
  9. alert(d); // [99,1,2] が出力される

前半が基本型。変数 a と b は違う値。後半が参照型の数値を使用するコード例。変数 c と d は同じ配列を参照している。

  1. var scope = "global";
  2. function f(){
  3. alert(scope); // 「undefined」が表示される
  4. var scope = "local"; // 初期値はここで設定される。宣言自体は関数全体で有効
  5. alert(scope); // 「local」が表示される
  6. }
  7. f();

グローバル変数と同じ名前のローカル変数が宣言された場合、グローバル変数はローカル変数で隠される。そして変数の宣言自体は、どこに書いたとしても関数全体で有効(値の代入は記述順に従う)。一般的なJavaScriptソースにおいて、変数宣言が冒頭に集中する理由はここにある。

参照: David Flanagan 著, 村上 列 訳, 『JavaScript 第五版

テスト

  1. // 準備
  2. var foo = 10;
  3. var hoge = function(foo){
  4. foo = foo + 100;
  5. alert(foo);
  6. };
  7.  
  8. // 実行
  9. hoge(foo); // 110
  10. alert(foo); // 10

(実行結果)

関数 hoge() 内で弄った変数の値は、hoge() 内でのみ有効。

  1. // 準備
  2. var foox = 50;
  3. var fooa = 10;
  4. var hoge = function(foo){
  5. var foob = foo + 100;
  6. alert(foox); // 50
  7. alert(foob); // 110
  8. };
  9.  
  10. // 実行
  11. hoge(fooa);
  12. alert(fooa); // 10
  13. alert(foob); // "foob is not defined"

(実行結果)

内側から外側にはアクセスできる。
外側から内側にはアクセスできない。

ちなみに、以下のように書くこともできる。

  1. // 準備
  2. var foo = 10;
  3.  
  4. (function(foo){
  5. foo = foo + 100;
  6. alert(foo);
  7. }).call(this, foo);
  8.  
  9. // 実行
  10. alert(foo);

なんかプロっぽい。
この場合、匿名関数の内側でスコープが一段下がっているため、匿名関数内で定義した変数はグローバルな名前空間を汚染しない。なので、その場限りで使い捨てることができる。便利。

オブジェクトの場合

  1. // 準備
  2. var foo = new Object();
  3. foo.x = 50;
  4. foo.a = 10;
  5.  
  6. var fxx = new Object();
  7. fxx.x = 50;
  8. fxx.a = 10;
  9.  
  10. var hoge = new Object();
  11. hoge.hoge1 = function(){
  12. this.z = foo.a + fxx.a;
  13. alert(this.z);
  14. }
  15. hoge.hoge2 = function(){
  16. foo.a = foo.a + 200;
  17. alert(foo.a);
  18. }
  19. hoge.hoge3 = function() {
  20. var num = foo.a + 300;
  21. var abc = 'variable';
  22. return num;
  23. }
  24.  
  25. // 実行
  26. hoge.hoge1(); // 20
  27. hoge.hoge2(); // 210
  28. alert(foo.a); // 210
  29. alert(hoge.z); // 20
  30. alert(hoge.hoge3()); // undefined
  31. alert(hoge.hoge3.abc); // "abc is not defined"
  32. alert(abc);

この例では、オブジェクトが3つあります。foo、fxx、そして hoge ですね。foo および fxx にプロパティをセットし、hogeのメソッドから値を変更してみてみます。

(実行結果)

つまり...

  • 他オブジェクトから値を取得できる
  • 他オブジェクトのプロパティを変更できる
  • プロパティにセットした値は保持される
  • メソッド内で新規作成したプロパティも保持される
  • メソッド内で return したものが返ってくる
  • メソッド内で定義した変数にはアクセスできない

で、まったく同じことを流行の書き方で書くとこうなる

  1. // 準備
  2. var foo = {
  3. x: 50,
  4. a: 10
  5. }
  6.  
  7. var fxx = {
  8. x: 50,
  9. a: 10
  10. }
  11.  
  12. var hoge = {
  13. hoge1: function(){
  14. this.z = foo.a + fxx.a;
  15. alert(this.z);
  16. },
  17. hoge2: function(){
  18. foo.a = foo.a + 200;
  19. alert(foo.a);
  20. },
  21. hoge3: function() {
  22. var num = foo.a + 300;
  23. var abc = 'variable';
  24. return num;
  25. }
  26. }
  27.  
  28. // 実行
  29. hoge.hoge1(); // 20
  30. hoge.hoge2(); // 210
  31. alert(foo.a); // 210
  32. alert(hoge.z); // 20
  33. alert(hoge.hoge3()); // undefined
  34. alert(hoge.hoge3.abc); // "abc is not defined"
  35. alert(abc);

Note: スパム対策が面倒なので、コメント投稿を廃止しました。以前のコメントは残します。
ご意見・ご要望はtwitter@sigwygかはてブコメントにて。