みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

グローバル変数とローカル変数について

今JavaScriptを勉強している初心者です。
実は、ある本に書かれているソースコードでグローバル変数とローカル変数の違いがよく分からなくなりましてここに質問させて頂く次第です。
まずは、ソースコードを書きます。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Script-Type" content="text/javascript">
<meta http-equiv="Content-type" content="text/html; charset=UTF-8">
<title>変数のスコープ</title>
<script type="text/javascript">
function testFunc(){
var num;
num=5;
}
</script>
</head>

<body bgcolor="#FFFFFF">
<p style="font-size:200%">
<script type="text/javascript">
var num=3;
testFunc();
document.write(num, "<br>")
</script>
</p>
</body>
</html>

このままだとブラウザに表示されるのはグローバル変数「3」になるというのも今一分からないのですが。
この次に<head>部分の
var num;
をコメントにします。
すると、何故かローカル変数「5」がグローバル変数になり、<body>部分のtestFunc()メソッドの結果ブラウザに表示される値が「5」になるというものです。
var num;
をコメントとする事はnumは変数宣言されていないことになります。
なのに
num = 5;
が成り立つのもよく分かりません。

そこでアドバイスを頂きたくここに書き込む事にしました。
是非とも宜しくお願い致します。

投稿日時 - 2011-05-01 22:45:58

QNo.6707823

すぐに回答ほしいです

質問者が選んだベストアンサー

実を言うと、その本の書き方は script 要素を別にしているため、HTML の読み込み順序規則も絡んで少々ややこしいことになっています。

純粋に JavaScript の規則だけで考えられるよう、素直にスクリプトコードを 1 個にまとめてしまいましょう。

var num = 3;
testFunc();
document.write(num, "<br>")

function testFunc(){
 var num;
 num = 5;
}

さて、JavaScript コードは単純に「上から下へ」実行されるわけではありません。まず、グローバルの位置にある var 宣言と function 宣言を全て拾い集めます。ここでは次の 2 個が見つかります。

// 宣言の処理
var num;
function testFunc();

その後、グローバルコードを「上から下へ」実行していきます。

// 実行ステップ
num = 3;
testFunc();
document.write(num, "<br>")

つまり、実行ステップに入る前に、var 宣言と function 宣言は変数と関数を作ってしまうのです。ただし、この段階では入れ物を作ったに過ぎず、実際に何を入れるかは実行ステップに入ってから決まります。var num = 3; とは、変数宣言である var num; と、実行ステップの num = 3; を一緒に書いたものにすぎません。私が冒頭でわざと function testFunc() を下に持って来た理由もお分かりでしょう。ソースコードの中で function 宣言を後ろに書いても、何の問題もないのです。

さて、このグローバルコードは実行ステップで関数 testFunc を呼び出しています。すると、実行ステップは一時中断され、testFunc に記述された関数コードが、やはり同じ手順で処理されます。まず、関数コード内の var 宣言と function 宣言を拾い集めます(仮引数も var 宣言と見なされます)。

// 宣言の処理
var num;

// 実行ステップ
num = 5;

実行ステップに入り、変数 num をセットしようとすると、グローバルの num が見つかる前に、関数内の num が見つかります。なので、書き変わるのは関数内の num だけです。

関数コードの実行ステップが終わると、グローバルコードの実行ステップに戻ります。document.write(num); で使われる num はグローバルで宣言された num であり、この場合は初期化された 3 のままです。

----
こうして、グローバルコードの変数・関数宣言 → グローバルコードの実行 → 関数呼び出しによるグローバルコードの一時中断 → 関数コードの変数・関数宣言 → 関数コードの実行 → グローバルコードの再開、のようになります。

関数コードは関数が呼び出されるたびに処理されます。つまり、関数を呼び出すたびにローカル変数が作られているのです。var 宣言が処理されるタイミングを念頭に置きつつ、ソースコードとにらめっこして下さい。

----
もし、関数 testFunc 内の var num; を削除した場合はどうなるか。関数コードは次のように処理されます。

// 宣言の処理
// 何もなし

// 実行ステップ
num = 5;

実行ステップに入り、変数 num をセットしようとすると、グローバルの num しか見つかりません。なので、グローバル変数の num が書き換えられます。

関数コードの実行ステップが終わり、グローバルコードの実行ステップに戻って document.write(num); を呼び出します。このときの num はグローバル変数ですが、上のステップで書き換えられて 5 になっています。

----
そういうわけで、JavaScript ではスコープが極めて重要であり、原則として必ず var 宣言して下さい。スコープチェーンの応用のひとつに、いわゆるクロージャと呼ばれるものがありますが、この辺りをすっ飛ばしていると理解しにくいと思います。

投稿日時 - 2011-05-02 08:54:41

お礼

再度の回答ありがとうございます。
細かく砕いた説明を読ませて頂き、何となく理解できたような気がします。
今勉強している本は少し端折っている部分があるためかどうかは分かりませんが、以前よりは大分前進した気がしました。
本当に心から感謝しています。

投稿日時 - 2011-05-02 15:26:43

ANo.5

このQ&Aは役に立ちましたか?

1人が「このQ&Aが役に立った」と投票しています

回答(8)

ANo.8

補足を読みました。

関数内で変数が書き換えられている場合でも「関数定義しただけでは書き換えが起こらない」という仕組みは理解されているでしょうか。
関数は定義するだけでは何もしません。呼び出されて初めて実行されるのです。
http://ideone.com/0CaJl

投稿日時 - 2011-05-02 17:41:23

補足

最初の部分の
『関数内で変数が書き換えられている場合でも「関数定義しただけでは書き換えが起こらない」という仕組みは理解されているでしょうか。』
に関しては多少難しくどういう事を言っているのかもう少し噛み砕いた説明があると大変助かります。
面倒かもしれませんが、その点に関してのアドバイスをどうぞ宜しくお願い致します。

投稿日時 - 2011-05-04 09:46:46

お礼

回答を頂き、感謝します。
貼り付けて頂いたURLのソースコードは理解できました。

投稿日時 - 2011-05-04 09:42:13

ANo.7

シンプルに

1) 関数外で変数を宣言するとグローバル変数になります。
2) 関数外で変数に代入すると、その変数が宣言されているいないに
かかわらずグローバル変数になります。
3) 関数内で変数を宣言するとローカル変数になります。
4) 同名のグローバル変数と、ローカル変数が有る場合、
ローカル変数を宣言した関数内では、その名前で変数を
参照すると、ローカル変数をアクセスします。
そうでなければグローバル変数をアクセスします。

蛇足

グローバル変数とはブラウザでは window グローバルオブジェクトの
プロパティです。
Chromeや FireFox などデバッガが使えるブラウザなら、window を
覗いてみてください。num というプロパティが出来ているはずです。

グローバル変数は結局グローバルオブジェクト(ブラウザでは window)の
プロパティ(ハッシュのkey-value項目)にすぎません(^^;

投稿日時 - 2011-05-02 13:49:30

お礼

回答を頂き、感謝します。
丁寧に噛み砕いたアドバイス恐れ入ります。
回答者様を含めいろいろな方々からのアドバイスから少しずつではありますが理解できているみたいです。
まだ少し不安はありますが、今は1つの法則として覚えておくつもりです。

投稿日時 - 2011-05-04 09:36:13

ANo.6

他の方がちゃんとネストやチェーンを説明してくれています。

ただ、私への補足をみるかぎり、そのネストの説明が理解できるか疑問です。

>var num; をコメントしただけグローバル変数

そこを説明したつもりなんですが・・・・

変数名 = 値

はなんだと思います。これは変数へ値を代入した。と言うのが正しく、変数の宣言及び初期化したわけではありません。

すでに記憶領域に、numと言うグローバル変数を直前に宣言していると、記載したはずですが?

他の方も、記載していますが、実行単位が、<script></script>単位ですが、記憶域には、testFunc();を実行するより先に、宣言されています。

なので、testFunc(); で、num=5 はグローバル変数として宣言されていません。最初の

var num;

が宣言され、どこからでも参照可能となっていると事で、他のユーザー関数で

num=値とすると、

---------------------------------
<script type="text/javascript">
var num=3;
----------------------------

で宣言さえた、グローバル変数に、値を代入したのです。testFunc(); でグローバル変数は宣言されていません。

{}の中で宣言された物、これは

var 変数名;

は{} の中だけでローカライズされ、他の同じ名前の変数とは別扱いになるが、var で初期化(宣言)しないと、同名グローバル変数があれば、その変数に値を代入し、なければ、{}中だけで通用する、変数が自動的に宣言され使用できるようになっている。

たったそれだけです。

さらに他の方が、説明している、実行単位とネストがあり、この実行順番の不確定さを、クリアーにするためには、

プログラム言語の、C++やJava、VBのように常に明示的に宣言をすることです。

逆に言えば、他の方がいっている

 ( func(){
    func(){
      return func() { }
  }
 )()

OBJECT.prototype.名前 {



のように、C++などの擬似的なクラス作成をすると、もっと使いやすくなります。

これだと継承とかエクステンド(いっしょか)が擬似的にできます。

次のステップとして、習いましょう。
のような、ことにすると
}

投稿日時 - 2011-05-02 09:58:11

お礼

再度の回答ありがとうございます。
今の状態ですが、回答者様を含めいろいろな方々からのアドバイスを頂いたおかげで少しずつではありますが、理解できているようなつもりでいます。
まだ不安なところはありますが、1つの法則というふうに頭にいれました。
ただ1つだけ、チェーンやネストなどの用語については全く説明が無く、分からないままなのが残念でなりません。
それはともかくとして、私のためにアドバイスを2回も頂けた事は大変感謝に値する事は事実であります。

投稿日時 - 2011-05-04 09:27:27

ANo.4

この手問題は、初心者にとってはわかりにくいかもしれません。

たぶん、JavaScriptでなくても、理解しにくいでしょう。

もともとJavaScriptでは、変数の宣言は必要に応じて行うのであって、しなければならないというものではない。

さらに、どのようなデーター型でも、比較さえしなければ(IFや括弧などの評価)、問題なく格納されてしまう。

にもかかわらず、比較又は評価しようとすると、突然データー型が邪魔をする。

C++, Java, アセンブラなどの言語を中心に使っているかただと、まったくもって難解なものである。

最初に戻すが、これを初心者からみると、とってもありがたい仕様になっている。

必要なときに、

1.
明示的に宣言する。
2.
評価するときだけ、データー型を意識して評価してくれる。
3.
宣言を忘れたり、間違っても、エラーにならない。

この初心者用の使い勝手を心得れば、なんでもないことなのです。

必要なと時は、明示的に、変数を初期化する事を意味しています。しなければ、その前に使われた変数をそのまま使います。

C言語などのスコープに当たるのは、ネストされているときだけですね。

>ローカル変数「5」がグローバル変数になり

といってますが、正確にはなっていないですよね。

<script type="text/javascript">
var num=3

でグローバル変数numが宣言され、どこのユーザー関数でも num の値が書き換える事ができるようになっている。

そこへ、testFunc(); で、var numとしていないため、グローバル変数の numの値を書き換えている。

testFunc() で、 var num とすれば、その関数内で有効な変数を宣言していたのに、なくなったから、その直前に宣言したグローバル変数の値を変更した。

と言うのが本当のところです。

つまり、勘違いしているわけです。

これで、最初に記載したことを思い出すと、必要なときだけ、宣言して初期化できる事が、かえってあだとなって、ユーザーが、グローバルなのか、ローカルなのか区別できなくなっている弊害を生んでいるわけです。

この手に悩む方は、オブジェクト指向のプログラミングになれないかもしれませんね。

投稿日時 - 2011-05-02 01:26:18

補足

補足と言うより、アドバイスを読み返して疑問に思った事を書きます。

変数はvarで宣言しなくても良いのは分かりましたが、
num = 5;
はtestFunc()メソッド内の変数であり、何故
var num;
をコメントしただけグローバル変数になってしまうのかが今一分かりません。
また、コメントをした後に<body>部分でグローバル変数
var num = 3;
では無く<head>部分の
num = 5;
の値がブラウザに表示されるのは同じグローバル変数の中でも優先順位で表示されていると考えて宜しいのでしょうか。
また、
var num;
をコメントしない場合、
num = 5;

var num = 3;
に置き換えられたと考えて宜しいのでしょうか。

幾つも質問してしまい申し訳ありませんが、質問に答えて頂けると幸いです。
それと、最後の文章に関してですが、
『この手に悩む方は、オブジェクト指向のプログラミングになれないかもしれませんね。』
これはどういう意味を指しているのでしょうか。

これらについて再度のアドバイスをどうぞ宜しくお願い致します。

投稿日時 - 2011-05-02 02:26:06

お礼

この度は丁寧な説明を書いて頂き、大変感謝しています。
何度か読み返して見たところ、納得できているのかどうか不安になり、補足を書かせて頂きました。
補足の部分に関しては無視して頂いても構いませんが、お時間がありましたらアドバイスをもらえるととても助かります。

投稿日時 - 2011-05-02 02:30:34

ANo.3

JavaScript の基礎にして最重要項目は 2 つ、「プロトタイプチェーン」と「スコープチェーン」です。ここではスコープチェーンを考えてみましょう。

スコープチェーンとは、変数を探すときの順番です。

var global;

function F () {
  var F_local;
  
  function G () {
    var G_local;
  }
}

ここで、関数 G 内で変数を取得しようとすると、まず G() 内の var 宣言を探し、次に F() 内の var 宣言を、最後にグローバルの var 宣言を探します。グローバルにも var 宣言が見つからなければエラーになります。このように、JavaScript では関数定義の入れ子関係を辿って変数を探します。

また、関数 G 内で変数をセットしようとすると、やはりまず G() 内を探し、次に F() 内を、最後にグローバルを探します。グローバルにも変数が見つからなければ、グローバルにその変数を作ってセットします。

したがって、var 宣言しないでセットされた変数は全てグローバル変数になってしまい、非常に危険です。var 宣言を疎かにする「入門書、解説書」が非常に多いですが、気を付けて下さい。

---
応用として次の例を考えてみましょう。

var a;

function F () {
  var a;
  
  function G () {
    var a;
  }
}

ここで、関数 G 内で変数 a を取得・セットしようとすると、その a は必ず関数 G 内のものになります。関数 F 内の a、およびグローバルの a には影響を与えません。

---
以上で謎は解けたと思いますが、いかがでしょうか。

function testFunc(){
 var num;
 num = 5;
}

var num = 3;
testFunc();
alert (num);

関数 testFunc 内で変数 num を探すと、testFunc 内で宣言された num が見つかります。testFunc 内の num をいくら変更しても、グローバル変数の num には影響を与えませんので「3」が表示されます。

もし testFunc 内の var num; を削ってしまうと、testFunc は一段上(ここではグローバル)の num を探すため、グローバルの num が書き換えられて「5」になります。

投稿日時 - 2011-05-02 01:19:06

補足

補足と言うより、アドバイスを読み返して疑問に思った事を書きます。

何となく理解できるのですが、ローカル変数をいくら変更してもグローバル変数の値が表示されるのはローカル変数
num = 5;

グローバル変数
var num = 3;
に置き換えたからでいいのでしょうか。
次に、
testFunc()メソッド内の
var num;
をコメントすると、グローバル変数
num = 5;
が作成され、グローバル変数同士だからブラウザには優先順位として「5」が表示されるのでしょうか。

幾つも質問をしてしまい誠に申し訳ありません。
私としては理解度は半々だと思っていますが、知識足らずの部分もあり、できればアドバイスを頂けると幸いです。
どうぞ宜しくお願い致します。

投稿日時 - 2011-05-02 02:46:06

お礼

この度は丁寧な説明を書いて頂き、大変感謝しています。
何度か読み返して見たところ、納得できているのかどうか不安になり、補足を書かせて頂きました。
補足の部分に関しては無視して頂いても構いませんが、お時間がありましたらアドバイスをもらえるととても助かります。

投稿日時 - 2011-05-02 02:46:59

ANo.2

<script type="text/javascript">
function testFunc(){※※101
var num;※1
num=5;※2
}
</script>
</head>

<body bgcolor="#FFFFFF">
<p style="font-size:200%">
<script type="text/javascript">※※100
var num=3;※3
testFunc();※4
document.write(num, "<br>")※5
</script>
</p>
</body>
</html>

こんな書き方いいとは思えませんが、
書き出したらきりがないので勉強用ということで。

>このままだとブラウザに表示されるのはグローバル変数「3」になるというのも今一分からないのですが。
※5の命令で参照しているのは※3の変数です。
※※101の※2で処理しているのは、※1です。
したがって、※3には何もしていません。
>この次に<head>部分の
>var num;
>をコメントにします。
>すると、何故かローカル変数「5」がグローバル変数になり、<body>部分のtestFunc()メソッドの
>結果ブラウザに表示される値が「5」になるというものです。
※1がコメント化されると、※2で処理するのは、グローバル変数の※3となります。
したがって、※101を実行した結果※3は5に変更されてしまいます。
ローカル変数があればそれを優先、なければグローバル変数を処理するということでしょう。

と書きつつ、現実には、すべてのブラウザで同じ結果になるとは保証できませんので
こんな書き方はしないことです。

投稿日時 - 2011-05-01 23:45:49

補足

補足と言うより、アドバイスを読み返して疑問に思った事を書きます。

ローカル変数よりグローバル変数の方が優先順位が高いので
var num;
をコメントするまではブラウザに「3」が表示されるのでしょうか。
もう1つ疑問なのが、
var num;
をコメントした後はtestFunc()メソッドが処理するのは
num = 5;
であり、
num = 3;
は優先順位の面から無視されるという事でしょうか。
大変申し訳ありませんが、この部分が引っかかっています。
面倒でなければ、このコメントをした後のnumの変化についてもう1度ご説明頂けないでしょうか。
大変お手数をおかけしますが、アドバイスをどうぞ宜しくお願い致します。

投稿日時 - 2011-05-02 03:02:00

お礼

この度は丁寧な説明を書いて頂き、大変感謝しています。
何度か読み返して見たところ、納得できているのかどうか不安になり、補足を書かせて頂きました。
補足の部分に関しては無視して頂いても構いませんが、お時間がありましたらアドバイスをもらえるととても助かります。

投稿日時 - 2011-05-02 03:02:37

ANo.1

JavaScriptはスコープの概念が無かったような気がしますが…

投稿日時 - 2011-05-01 23:21:24