JavaScript入門 スコープ
スコープとは、実行中のコードから参照できる変数や関数の範囲のことです。変数や関数を参照(使用)出来る範囲はスコープによって決まっています。
別のスコープに存在する変数や関数は参照不可
function fnScopeA(){
let vaLa = 100;
}
function fnScopeB(){
console.log( vaLa ); //エラーが発生する
}
fnScopeB();
JavaScriptスコープ
種類 | 概要 |
---|---|
グローバルスコープ | JavaScriptファイルのトップレベル、またはHTMLファイルのscriptタグの直下で、「varを使って定義された変数や関数」または「関数宣言によって定義された関数」が属するスコープ |
スクリプトスコープ | JavaScriptファイルのトップレベル、またはHTMLファイルのscriptタグの直下で、letやconstを使って宣言された変数や関数が属するスコープ |
関数スコープ | 関数内で宣言された変数や関数が属するスコープ |
ブロックスコープ | if文やwhile文などのブロック内で、letまたはconstで宣言された変数や関数が属するスコープ |
モジュールスコープ | ESModulesの機能が有効なときに、JavaScriptファイルのトップレベル、またはHTMLファイルのscriptタグの直下で宣言された変数や関数が属するスコープ |
スコープ条件一覧
トップレベル | 関数内 | ブロック内 | モジュール内 トップレベル | |
---|---|---|---|---|
let | スクリプトスコープ | 関数スコープ | ブロックスコープ | モジュールスコープ |
const | スクリプトスコープ | 関数スコープ | ブロックスコープ | モジュールスコープ |
var | グローバルスコープ | 関数スコープ | ブロックスコープの外側のスコープに配置 | モジュールスコープ |
関数宣言 | グローバルスコープ | 関数スコープ | ブロックスコープの外側のスコープに配置 | モジュールスコープ |
関数スコープ
関数内で宣言された変数や関数は、関数はスコープに配置されます。関数の引数も関数スコープに含みます。関数スコープは、関数ごとに作成されるため、異なる関数スコープに配置された変数や関数を使うことはできません。
function fnScopeA(){
let vaLa = 100;
const SubA = function(){ console.log("fnA内の関数") };
console.log( vaLa ); //同じスコープで宣言されているため実行可能
SubA (); //同じスコープで宣言されているため実行可能
}
function fnScopeB(){
SubA (); //スコープ外のためエラー発生する
console.log( vaLa ); //スコープ外のためエラーが発生する
}
fnScopeB();
ブロックスコープ
ブロックスコープは、if文やfor文などのブロック{}内で、letまたはconstキーワードを使って宣言された変数や関数が属するスコープです。なお、varを使って宣言された変数や関数宣言で定義された関数は、ブロックスコープを無視して、1つ外側のスコープに属することになります。また、ブロックスコープに属する変数や関数を参照することはできません。
if( true ){
let letVal = "letで宣言";
const constFn = function () { console.log( "constで宣言" ); };
var varLal = "varで宣言";
function fnStmt() { console.log( "関数宣言" ); }
}
if( true ){
console.log( letVal ); //エラー発生
constFn(); //エラー発生
console.log( varVal ); //取得
fnStmt(); //取得
}
letやconstを使って宣言した変数や関数は他ブロックスコープから取得できませんが、varや関数宣言を使った場合は、他ブロックスコープから参照できます。
グローバルスコープ
グローバルスコープは、「JavaScriptファイルのトップレベル、またはHTMLファイルのscriptタグのの直下に記述したコード」内で、「varを使って定義された変数や関数」または「関数宣言(function(){・・・})によって定義された関数」が属するスコープ
グローバルスコープに配置された変数や関数は、コードのどこからでも参照可能です。
<script>
var globalVal = "グローバル変数"; //トップレベルで変数を宣言
function globalFn(){ return "グローバル関数"; } //トップレベルで関数を宣言
function callGolobals(){
console.log( globalVal ,globalFn() ); //関数内から参照
}
callGlobals();
>グローバル変数 グローバル関数
</script>
グローバルスコープに配置された変数globalValや関数globalFnが関数callGolobals内から問題なく参照できます。
グローバルスコープに配置された変数や関数は、異なるscriptタグからも参照可能です。
<script>
var globalVal = "グローバル変数";
function globalFn(){ return "グローバル関数"; }
</script>
<script>
console.log( globalVal ,globalFn() ); //異なるscriptタグから参照
</script>
これは<script src=”JavaScriptファイル”>のようにファイルを読み込む形で実行された場合も同様です。
グローバルスコープとWindowオブジェクト
Varや関数宣言を使って定義した変数や関数は、Windowオブジェクトというオブジェクトのプロパティやメソッドとして値が保持されます。
var globalVal = "グローバル変数";
function globalFn(){ return "グローバル関数"; }
console.log( globalVal ,window.globalVal () ); //windowオブジェクトのプロパティ
console.log( globalFn(),window.globalFn() ); //windowsオブジェクトのメソッド
上記のコードから、トップレベルでvarを使って宣言した、windowオブジェクトのプロパティとしても取得できることがわかります。そして、このことから、JavaScriptのWindowオブジェクトに格納されているプロパティやメソッドは、windowを省略して記述できます。たとえば、setTimeoutはJavaScript エンジンの組み込み関数ですが、この関数を実行するときにwindowを付けなくても同じ意味になります。
//windowを付けた場合
window.setTimeout( function() {
consle.log( "おはよう" );
}, 3000);
//windowを付けなかった場合
setTimeout( function() {
consle.log( "おはよう" );
}, 3000);
その他にも、NumberやBooleanなどの組み込み関数も同様です。
スクリプトスコープ
ES6で追加されたスクリプトスコープは、「JavaScriptファイルのトップレベル、またはHTMLファイルのscriptタグの直下に記述したコード」内で、「letやconstを使って定義された変数や関数」が属するスコープです。グローバルスコープと同様、スクリプトスコープに配置された変数や関数もコード内のどこからでも参照可能なため、基本的にはグローバルスコープと区別せずに使うことができます。
<script>
let scriptVal = "スクリプト変数";
function scriptFn(){ return "スクリプト関数"; }
</script>
<script>
console.log( scriptVal ,scriptFn() ); //異なるscriptタグから参照
</script>
>スクリプト変数 スクリプト関数
ただし、スクリプトスコープに配置された変数や関数は、グローバルスコープのときと違い、Windowオブジェクトのプロパティとして格納されることはありません。
スクリプトスコープとグローバルスコープに同名の変数があった場合
スクリプトスコープとグローバルスコープに同名の変数があった場合は、スクリプトスコープの値が優先されます。
let globalVal = "スクリプト変数";
window.globalVal = "グローバル変数";
console.log( globalVal );
>スクリプト変数
モジュールスコープ
モジュールスコープは、ES6で追加されたES Modulesという機能を有効化したときに生成されるスコープです。ES Modulesの機能を有効化するには、scriptタグに対してtype=”module”を設定します。
<script type="module">
/* ES Moduleが有効化 */
</script>
これによってモジュールスコープが有効になるため、JavaScriptファイルのトップレベル、またはHTMLファイルのscriptタグの直下で宣言された変数や関数は、モジュールスコープに属することになります。モジュールが有効になるとscriptタグの単位でモジュール形成され、他のモジュール内の変数や関数を参照できなくなります。
<script type="module">
let moduleVal = "モジュールスコープの変数";
console.log( moduleVal );
</script>
<script type="module">
console.log( moduleVal ); //エラーが発生
</script>
即時関数
ES6でES Modulesが追加される前は、即時関数を使ってスコープを制限していました。即時関数は、関数定義と同時に関数の実行を行う特殊な記法です。
構文
( function( [仮引数1,仮引数2,・・・] ) ){
/* この部分のコードが定義と同時に実行される */
})( [ 実引数1 , 実引数2, ・・・ ] );
( function fnA() {
const val = "即時関数外からは参照不可";
})();
( function fnB() {
console.log( val ); //エラーが発生
})();
レキシカルスコープ
JavaScriptでは、同一スコープ以外にも、レキシカルスコープに配置された変数や関数は参照できます。レキシカルスコープとは、記述する場所によって、参照できる変数が異なるスコープの事を意味しますレキシカルスコープは、実行中のコードが属するスコープの外側のスコープになります。
自スコープでなくても、レキシカルスコープに配置された変数や関数は参照することが可能です。
<script>
let scriptVal = "スクリプトスコープ";
function fn(){
let fnVal = "関数スコープ";
if ( true ) {
console.log( scriptVal,fnVal ); //※1
console.log( scriptFn()); //※2
console.log( sVal ); //※3
}
}
function scriptFn(){
let sVal = "scriptFn";
return "scriptFnの実行結果";
}
fn();
</script>
- ※1 scriptVal,fnValともにレキシカルスコープの変数であるため参照可
- ※2 scriptFn()は、レキシカルスコープであるグローバルスコープの関数であるため参照可
- ※3 sValは、scriptFn内で宣言されているので、レキシカルスコープの変数ではありません。そのため、エラーが発生します。
このように自スコープの外側のスコープがレキシカルスコープとなるため、レキシカルスコープは外部スコープや親スコープとも呼びます。また、記述した時点で決定するスコープのため、静的スコープと呼ぶこともあります。
スコープチェーン
レキシカルスコープに同じ名前の変数が複数存在した場合の挙動ついて記載します。
<script>
window.whichVal = "グローバルスコープ";
let whichVal = "スクリプトスコープ";
function outerScode() {
let whichVal = "outerScope関数スコープ";
function innerScode(){
let whichVal = "innerScope関数スコープ";
console.log( whichVal );
}
innerScode();
}
outerScode();
</script>
上記のコードでは、自スコープ(innerScopeの関数スコープ)に存在するwhichValの値が出力されます。しかし、仮にwhichValが自スコープに存在しなかった場合には、JavaScriptエンジンは外側のスコープを1つずつたどってwhichValの変数を探しにいき、見つかった時点でその変数をwhichValの参照先の値として利用します。
このような、レキシカルスコープが他階層に連なっている状態をスコープチェーンと呼びます。変数が自スコープに見つからない場合は、スコープチェーンをたどって変数を探しに行いきます。グローバルスコープまで変数を探してなかった場合は、エラーとなります。
クロージャ
クロージャ(Closure)とは、関数内で使用されている変数がレキシカルスコープの変数の値を保持し続ける状態です。関数内で使われる引数や変数は、関数が終了すると関数外から参照できないため、不要とみなされて定期的にメモリから削除されます。この処理はガベージコレクションと呼び、JavaScriptエンジンよって自動的に行われます。
そのため、一般的に関数内で宣言された変数は関数の終了とともに参照不可能になりますが、次のコードのようにレキシカルスコープの変数が保持する値を使用している関数が戻り値として実行元に返された場合には、その値はガベージコレクションの対象とならず、保持し続けます。
function factory( greeting ){
function innerFn( name ){
console.log( greeting + "" + name );
}
return innerFn;
}
const hello = factory( "ハロー" );
hello( "山田太郎" );
>ハロー山田太郎
このコードの実行は、次のようになります
- 関数factoryが実行されます。このとき、関数factoryのgreetingには、”ハロー”が渡されます。
- 関数factory内のコードが実行されるときに、関数innerFnが宣言されます。innerFn内ではgreetingが使われているため、レキシカルスコープのfactoryの引数のgreetingの値(”ハロー”)がinnerFn内のgreetingが参照する先の値となります。
- 関数innerFnは戻り値として実行元に返されます(関数はオブジェクトの一種なので、戻り値としても返すことができます。)。そのため、変数helloには関数factory内で宣言された関数innerFnが代入されます。
- 関数helloを実行すると、innerFn(②の部分)が実行されます。このときのgreetingの値は、②で取得したレキシカルスコープのgreetingの値(”ハロー”)がそのまま保持されている状態になります。
本来であれば、関数factoryの実行完了とともに、引数greetingが保持する値はメモリから削除されます。しかし、今回の場合はinnerFn内でレキシカルスコープのgreetingの値を使用しており、さらに、innFnが関数factoryの戻り値として実行元に返されるため、innerFnが④のように外部から実行される可能性があります。そのため、このような状態では「innerFn内のgreetingが参照する先の値(”ハロー”)」はガベージコレクションの対象にならず残り続けることになります。この状態をクロージャと呼びます。このようにクロージャを利用することで、処理内の異なる関数を動的に作成できます。
クロージャで動的に関数を作成
function factory( greeting ){
・・・処理
}
const hello = factory( "ハロー" );
hello( "太郎" );
>ハロー太郎
const bye = factory( "バイバイ" );
bye( "太郎" );
>バイバイ太郎
つまりは、関数factoryに渡す引数によって、処理内容が異なる2つの関数helloとbyeを作成しています。そのため、関数helloとbyeをそれぞれ関数宣言するのではなく、関数factoryの定義だけで、異なる挙動の関数を作成できます。また、クロージャを利用すると、継続的に値の状態を保存できるため、関数内で使う値の状態を保持し続けたいときに有効です。
次の例では、incrementFactoryのreturnに続く無名関数内でレキシカルスコープの変数countの値を使用しています。これによって、increment();を実行したときには変数countの値は継続して保持されるため、++countによって現状のcountの値に対して1加算した結果がコンソールに表示されます。
function incrementFactory(){
let count = 0;
return function(){ //無名関数(関数A)をincrementFactoryの実行結果として返す
console.log( ++count ); //レキシカルスコープのcountの値を使用
}
}
const increment = incrementFactory();
increment(); //この関数の実行は関数Aの実行を意味する
>1
increment();
>2
increment();
>3