前回、前々回と各言語の「クセが強い」ところを触ってます。今回はその流れでJavaScriptであります。JavaScriptの場合、使っている人が多いので「クセが強い」でなく「コモンセンス」なのかも知れません。必要に迫られてJavaScriptのコード断片をたまに書くような年寄りには御無体な感じがしないでもないデス。
※「やっつけな日常」投稿順 indexはこちら
※動作確認は、WSL1上の Ubuntu-20.04 で走らせている nodejs v10.19.0 で行っています。
※キチンとした説明への入り口は、以下のページがテキトーかと
慣れないんだ、JavaScriptの「スコープ」
多くの言語、特に古典的なCなどはブロック・スコープが基本です。年寄りはこれに慣れきってますな。ホンワカした言い方すると、 { と } の中で宣言された変数(オブジェクト)は、{ と } の中でのみ有効、と。 お陰で遠く離れた場所にある別の { } の中のことを気にする習慣はありませぬ。
しかし、古典的なJavaScript(モダーンなJavaScriptは let があるので「ブロック・スコープ出来てしまう)で var つかって変数宣言すると関数スコープとて、{ と } の範囲を超え、注意力散漫、記憶力減退の年寄りには過ぎたる有効範囲が広がっとります。
そして「巻き上げ」、Cの固陋な順序が懐かしい
そして「巻き上げ」デス。これまた古典的なCと比べてしまいます。Cの場合、何か宣言したら、宣言したものを使えるようになるのは宣言した後です。ソースコードの順番で後の方で定義されている関数を前の方で定義されている関数から呼び出したい場合、プロトタイプ宣言などというカリソメの定義をわざわざその前に置き、「宣言してから使う」という絶対のルールを強制してますな。固陋だけれども依存関係はハッキリしておるような(個人の感想です。)
しかしJavaScriptには「巻き上げ」あり。後の方で定義されている関数が定義より前で使えるのは便利なので許すとして、どこかのブロック、 { と }の間でvar 宣言されているオブジェクトすらそれより前に「存在」している、と。まあ、初期化、代入は、実行される位置で行われるにせよ、メモリ上には居る。よって、宣言しているように見える部分より順序的には先に操作できるっと。
慣れたらきっと自由度高くてなんでもできる気がするのでしょうが、固陋なC頭にはハラハラする感じであります。
殊更な「巻き上げ」サンプルプログラム
以下に、ことさらに「巻き上げ」を強調するサンプルプログラムを書いてみましたです。
-
- 関数定義よりも先に関数を呼び出して使っている(関数巻き上げ)
- 関数の中で、グローバルスコープの変数に変数宣言より前に代入している(変数巻き上げ)
- 変数宣言は該当の変数の値により条件付きで実行される{ } 内にある(実行されなくても最初から実体はある)
- { }を脱出した後で、{ } 内の変数にアクセスしている(スコープが広い)
なんだかサッパリなプログラムが以下に
console.log("sample0=", sample0(1)); console.log("sample1=", sample1(2)); function sample0(arg) { temp = arg + 11; return temp; } function sample1(arg) { temp += arg; return temp; } console.log("temp_before=", temp); if (temp > 0) { var temp = 0; } console.log("temp_after=", temp);
実行結果が以下に。予定通りの挙動です。
以下のような理解で良いのですかね?
-
- 変数にせよ、関数にせよ、コンパイル時にメモリを割り当てられるので、変数宣言、関数定義の場所より前でもスコープの中に「存在」はする。これが「巻き上げ」の実体。よって変数に代入すること、関数を呼び出すことは可能。変数は巻き上げられただけであれば、未初期化状態なので読み出せば未定義となる。
- 変数宣言時の初期化(代入)は、該当の場所にいたって実行されないと行われない。つまり変数の値そのものは実行順序どおり。
つい気を抜くと忘れてしまいそうなJavaScriptの怖いところ。まあ、var 使わずにみな let にすればよいのか?ホントか?