こんにちは!Webコーダー・プログラマーの貝原(@touhicomu)です。
今回は、JavaScriptの変数のスコープについて解説したいと思います。
スコープにはグローバルスコープとローカルスコープがあり、それぞれの使い分けが初心者の方には難しく感じるかと思います。
そこで、この記事では
・変数のスコープとは
・様々な変数のスコープ
という基本的な内容から
・変数のスコープが存在しないケース
・変数のスコープを定義する文法
という実践的な内容までを解説していきます。
変数のスコープについて正しく理解し、必要な場面で使いこなせるように、しっかり学習を進めていきましょう。
変数のスコープとは
変数のスコープとは、その変数が参照できる範囲のことです。
スコープには、大きく分けて以下の2種類があります。
- グローバルスコープ → ページ全体でどこからでも参照できる。
- ローカルスコープ → ページ内の部分的な範囲のみ参照できる。
特にローカルスコープの変数は、変数名を使い回すことができ、局所的な範囲での一時的な変数の利用に適しています。
具体的なコードでみていきましょう。
以下のコードでは、先頭行で、グローバル変数 valGlobal1 を定義し値を代入しています。
ここで、関数 scopeUse() 内では、関数内で一時的に使用するためにローカル変数 valGlobal1 を定義し値を代入しています。
valGlobal1 = "global1"; window.onload = function () { // スコープとは function scopeUse() { var valGlobal1 = "local"; console.log("in local valGlobal1 = " + valGlobal1); } scopeUse(); }
実行結果:
in local valGlobal1 = local
以上のように、関数 scopeUse() のローカル変数 valGlobal1 が、グローバル変数 valGlobal1 よりも優先して使用されています。
まさに、この関数内など局所的な範囲内での変数の利用時に、グローバル変数であるかどうか考慮せずに、安全にローカル変数として使用できるところに、スコープのありがたみがあります。
様々な変数のスコープ
グローバルスコープ
グローバルスコープの変数は、JavaScriptのトップの位置に、varをつけずに、
valGlobal1 = “global1”;
もしくは、varをつけて、
var valGlobal2 = “global2”;
と宣言します。
グローバルスコープの変数は、ページ内のJavaScriptのどこからも参照可能です。
以下のコードでは、グローバルスコープの変数を、関数内から参照し、コンソールに出力しています。
また関数内でグローバルスコープの変数の値の変更も可能です。
valGlobal1 = "global1"; var valGlobal2 = "global2"; window.onload = function () { // グローバルスコープ function globalValue1() { console.log("valGlobal1 = " + valGlobal1); console.log("valGlobal2 = " + valGlobal2); // グリーバルスコープ変数を変更 valGlobal2 = "global3"; console.log("valGlobal2 modified = " + valGlobal2); } globalValue1(); }
実行結果:
valGlobal1 = global1 valGlobal2 = global2 valGlobal2 modified = global3
以上のように、関数内からグローバルスコープの変数を使用できていますね。
また、グローバル変数を使う場合は1点注意点があります。
グローバル変数はどこからでも使えるためとても便利ですが、どこからでも使える都合上変数名の衝突が起きやすいです。
たとえば、javascriptの新しいプラグインを入れた場合などに競合が発生し、予期せぬ動きになる場合があります。
多用しすぎには注意が必要のため、以降で説明するローカル変数の書き方を習得して一部のみグローバル変数を使うようにしてください。
関数スコープ
関数内でのスコープはローカルスコープの一つで関数スコープといいます。
関数内でvarをつけて定義した変数はローカル変数になります。
以下コードで確認してみましょう。
valGlobal1 = "global1"; var valGlobal2 = "global2"; window.onload = function () { // 関数スコープ function functionScopeValue1() { console.log("valGlobal1 = " + valGlobal1); console.log("valGlobal2 = " + valGlobal2); var valGlobal2 = "local2"; console.log("valGlobal2 to local2 = " + valGlobal2); } functionScopeValue1(); }
実行結果:
valGlobal1 = global1 valGlobal2 = undefined valGlobal2 to local2 = local2
関数内でもグローバル変数 valGlobal1 は使用できていますね。
実行結果の2行目は、
valGlobal2 = undefined
となっています。
valGlobal2の宣言は関数の4行目で行われています。
ローカル変数の宣言前にローカル変数と見なされるのは、関数内で定義した変数は、関数の頭で宣言したものとみなされるという仕様によります。
この仕様のことを、JavaScriptのホイスティング(巻き上げ)といいます。
ホイスティング対象の変数はvarつきでローカル変数として関数の4行目で定義されています。
var valGlobal2 = “local2”;
ローカル変数 valGlobal2 は、関数の4行目で値 “local2” が代入されていますが、ホイスティングが起こる関数の2行目では値が入っておらず、結果undefinedという値が表示されます。
関数の4行目でローカル変数に値を入れた後のconsole.log()の結果である、実行結果の3行目では、正しく値が設定され表示されていますね。
scriptタグのスコープ
scriptタグで分割して、グローバル変数を書いた場合にも、正しくグローバルスコープの変数として、ページ全体から変数を参照できます。
<script type="text/javascript"> valGlobal1 = "global1"; var valGlobal2 = "global2"; </script> <script type="text/javascript"> function globalValue2() { console.log("valGlobal1 = " + valGlobal1); console.log("valGlobal2 = " + valGlobal2); // グリーバルスコープ変数を変更 valGlobal2 = "global3"; console.log("valGlobal2 modified = " + valGlobal2); } globalValue2(); </script>
実行結果:
valGlobal1 = global1 valGlobal2 = global2 valGlobal2 modified = global3
以上のように、scriptタグをまたがっても、グローバル変数を参照できていますね。
またグローバル変数の値の変更もできています。
クロージャーのスコープ
クロージャーは関数を定義するのとほぼ同じですが、一つ上のブロックの変数にアクセスできます。
そのため、クロージャーと変数を組み合わせて簡潔に一時的に使用する関数を定義する場合などに使用されます。
クロージャーのスコープは、クロージャー内のブロックのスコープではなく、クロージャーの定義された一つ上のブロックのスコープです。
そのため、以下のコードでは、クロージャーの定義されているブロック内の変数 modifiedStr にクロージャーからアクセスできています。
クロージャーのこの特性はよく利用されます。
window.onload = function () { // クロージャースコープ function closureScope (str) { var modifiedStr = "***" + str + "***"; var closure = function () { return "###" + modifiedStr + "###"; } return closure(); } console.log("closure return = " + closureScope("apple")); }
実行結果:
closure return = ###***apple***###
以上のように、クロージャーの定義されたブロックの変数にクロージャーがアクセスできていますね。
即時関数のスコープ
即時関数とは、関数定義を技巧的に使用してブロックスコープを実現する方法です。
通常、JavaScriptにはブロックスコープは存在しませんが、即時関数を使用することで可能になります。
即時関数とは、
(function () {
・・・即時関数内コード・・・
})();
のようなコードになります。
window.onload = function () { //即時関数スコープ (function () { var immidateValue1 = "immidiate"; function immidiateScope() { return "***" + immidateValue1 + "***"; } console.log("immidate return = " + immidiateScope()); })(); console.log("immidateValue1 = " + immidateValue1); }
実行結果:
immidate return = ***immidiate*** Uncaught ReferenceError: immidateValue1 is not defined
以上のように、即時関数内で定義された変数 immidateValue1 は即時関数内では使用できますが、即時関数外では、実行結果の2行目で immidateValue1 is not definedエラーが表示されているように使用できません。
これで、即時関数内はブロックスコープの変数を扱えることがわかりました。
変数のスコープが存在しないケース
if文のスコープ
JavaScriptではif文にはブロックスコープは存在しません。
if文内で定義したローカル変数はif文のブロック外でも使用可能です。
以下、コードで確認してみましょう。
window.onload = function () { // if文でのスコープ if (true) { // if文内のスコープ var valIf = "if"; } // if文外でもアクセス可能 console.log("if out scope val = " + valIf); }
実行結果:
if out scope val = if
以上のように、if文で定義したローカル変数をif文のブロック以外でも使用できてしまいますね。
for文のスコープ
JavaScriptではfor文にもブロックスコープは存在しません。
for文内で定義したローカル変数はfor文のブロック外でも使用可能です。
以下、コードで確認してみましょう。
window.onload = function () { // for文でのスコープ for (var i = 0; i < 1; i++) { // for文内のスコープ var valFor = "for"; } // for文外でもアクセス可能 console.log("for out scope val = " + valFor); }
実行結果:
for out scope val = for
以上のように、for文で定義したローカル変数をfor文のブロック以外でも使用できてしまいますね。
変数のスコープを定義する文法
with
JavaScriptでは、with句を使用することでブロックスコープを実現できます。
with句は、以下のように書きます。
with ({ valWith: “” } ) {
・・・文・・・
}
変数 valWith は、この with 句内でのみ有効なブロックスコープのローカル変数です。
window.onload = function () { // withによるローカルスコープ with ({ valWith: "" } ) { valWith = "with"; console.log("with in scope val = " + valWith); } // withブロック外ではアクセスできない console.log("with out scope val = " + valWith); }
実行結果:
with in scope val = with Uncaught ReferenceError: valWith is not defined
実行結果の1行目は、with句内でのconsole.log()の出力で、正常に出力されています。
しかし、実行結果の2行目は、with句外で変数valWithを参照しているため、valWith is not definedエラーが出力されています。
このように、with句により、ブロックスコープの変数を使用できます。
let
letもブロックスコープの変数を実現する文法です。
ただし、ECMAJavaScript2015(ES6)以降からのサポートとなります。
letの使い方は簡単で、今までローカル変数を定義していた方法varの代わりにletを使うだけです。
letで宣言された変数は、自動的にローカル変数になります。
以下、コードで確認してみましょう。
window.onload = function () { //letによるローカルスコープ // if文でのスコープ(ブロックスコープ) if (true) { // if文内のスコープ let valIf2 = "if2"; console.log("let in scope val = " + valIf2); } // if文外ではアクセスできない console.log("if out scope val2 = " + valIf2); }
実行結果:
let in scope val = if2 Uncaught ReferenceError: valIf2 is not defined
実行結果の1行目は、if文のブロック内でletで定義された変数の出力で、正常に処理されていますね。
実行結果の2行目は、if文のブロック外で変数にアクセスした場合で、valIf2 is not definedエラーが出力されていますね。
以上のように、letによりブロックスコープのローカル変数が定義できることが分かりました。
まとめ
いかがでしたでしょうか。
変数のスコープにはグローバルスコープとロ-カルスコープがあり、変数名のかぶりを解消し、また、関数内など局所的に一時的に使用するローカル変数を利用するために便利でした。
特にローカルスコープは、コードの他の箇所に影響を与えないため、大変便利に使用できます。
ローカルスコープには、関数スコープ、クロージャーのスコープ、即時関数のスコープ、withのブロックスコープ、letのブロックスコープなどがありました。
種類がが多くて覚えることは大変ですが、コードを書きながら覚えていきましょう。
また、withやletではブロックスコープを実現できました。
しかし、if文やfor文などにはブロックスコープは実現できませんでした。
このあたりは、他の言語と大きな違いですので、扱いには注意しましょう。
JavaScriptのスコープについて忘れてしまったら、またこのページをご覧ください!