はじめに
今回は、JavaScriptにおける「スコープの概念」についてJavaScript本格入門の内容から重要な要素をまとめて行きます。
スコープ
スコープとは、変数がスクリプトの中のどの場所から参照できるかを決める概念である。
JavaScriptのスコープの分類
- スクリプト全体から参照できグローバルスコープ
- 定義された関数の中でのみ参照できるローカルスコープ
- ブロックスコープ(ES6以降)
グローバル変数
グローバルスコープをもつ変数のこと
ローカル変数
ローカルスコープをもつ変数のこと
グローバル変数とローカル変数の違い
- 関数の外で宣言した変数 => グローバル変数
- 関数の中で宣言した変数 => ローカル変数
▼ サンプルコード
var scope = "Global Variable";
function getValue() {
var scope = "Local Variable";
return scope;
}
console.log(getValue()); //結果:Local Variable
console.log(scope); //結果:Global Variable
変数宣言にvar命令が必要な理由
変数宣言がなくてもJavaScriptはエラーを表示させず、処理を実行してしまう。
とはいえ、変数宣言を行わないことによって意図した結果を返してくれないケースがある。
▼ サンプルコード
scope = "Global Variable";
function getValue() {
scope = "Local Variable";
return scope;
}
console.log(getValue()); //結果:Local Variable
console.log(scope); //結果:Local Variable
重要ポイント
var命令を使わずに宣言された変数はすべてグローバル変数とみなす
最初に定義されたグローバル変数scopeは、getValue関数が実行された段階で上書きされてしまう。
「var命令で定義された変数は、定義する場所によって変数のスコープが決まる」
=> ローカル変数を定義するには、必ずvar命令を使用する
無用なバグを発生させないために変数宣言を行うときはvar命令をグローバル、ローカル問わず必ずつけるようにする。
ローカル変数の有効範囲はどこまで?
▼ サンプルコード
var scope = "Global Variable";
function getValue() {
console.log(scope); //結果:undefined
var scope = "Local Variable";
return scope;
}
console.log(getValue()); //結果:Local Variable
console.log(scope); //結果:Global Variable
JavaScriptではローカル変数は「関数全体で有効」であるため、ローカル変数scopeは有効になっている。
しかし、ローカル変数が確保されているだけで、var命令は実行されておらず、ローカル変数の中身は未定義(undefined)である。
このような挙動のことを、変数の巻き上げという。
重要ポイント
ローカル変数は関数の先頭で宣言する
仮引数のスコープ
仮引数とは、呼び出し元から関数に渡された値を受け取るための変数である。
function getTriangle(base, height){・・・}
上記、getTriangle関数では、baseとheightが仮引数である。
基本型のグローバル変数とローカル変数の関係性
▼ サンプルコード
var value = 10; //グローバル変数valueを定義
function decrementValue(value) { //仮引数valueを持つdecrementValue関数
value--;
return value;
}
console.log(decrementValue(100)); //結果:99
console.log(value); //結果:10
関数内部で使用されている仮引数valueはローカル変数と見なされる。
そのためdecrementValue関数で仮引数valueに100を渡しても、仮引数valueをデクリメントしても、グローバル変数valueが書き換えられることはなく、定義されていた10が返される。
参照型のグローバル変数とローカル変数の関係性
▼ サンプルコード
var value = [1,2,4,8,16]; //配列を要素にもつ、グローバル変数valueを定義
function deleteElement(value) { //関数内で定義された仮引数value(ローカル変数)
value.pop(); //末尾の要素を削除
return value;
}
console.log(deleteElement(value)); //結果:[1,2,4,8]
console.log(value); //結果:[1,2,4,8]
参照型とは、「値そのものではなく、値を格納したメモリ上の場所だけを格納している型」である。
参照型の値を受け渡しする場合には、渡される値も(値そのものではなく)メモリ上のアドレス情報だけとなる。
=> 参照渡しという。
グローバル変数valueと関数内部で仮引数(ローカル変数)として定義されたvalueは変数として別物であるが、グローバル変数valueの値が仮引数valueに渡された時点で、結果的に実際に参照しているメモリ上の場所が等しくなる。
そのため、deleteElement関数の中で配列を操作した場合、その結果はグローバル変数valueにも反映されることになる。
即時関数で変数名の競合を防ぐ
▼ サンプルコード
(function() {
var i = 5;
console.log(i); //結果:5 即時関数
}).call(this); //callbackで即時実行
console.log(i); //変数iはスコープ外なのでエラー
重要ポイント
「変数の意図せぬ競合を防ぐ」ために変数のスコープをできるだけ必要最小限にとどめる。
ブロックスコープに対応したlet命令
ES2015で追加されたlet命令は。ブロックスコープに対応した変数を宣言する。
▼ サンプルコード
if(true) {
let i = 5;
}
console.log(i); //結果:エラー
let命令で宣言された変数はブロックの外では無効になり、結果はエラーになる。
「スコープはできる限り限定すべき」というルールからすれば、ES2015が利用できる環境ではvar命令ではなく、let命令を利用するのが望ましい。
またconst命令で定義された変数もブロックスコープをもつ。
即時関数は使用しない
該当するコードをブロックでくくり、配下の変数をlet命令で宣言すれば即時関数と同じ効果を得られるため、ES2015の環境では、即時関数は必要なくなる。
{
let i = 5;
console.log(i); //結果:5
}
console.log(i); //変数iはスコープ外なのでエラー
switchブロックでのlet宣言に注意
switch命令は、条件分岐全体として1つのブロック(caseはラベルで修飾された句であり、ブロックではない)のため、case句の単位にはlet宣言した場合にはエラーとなる。
switch(x) {
case 0:
let value = "x:0";
case 1:
let value = "x:1"; //変数名の重複
}
上記のようなケースではswitchブロック外で最初に変数valueを宣言しておくようにする。
関数リテラル/Functionコンストラクターにおけるスコープの違い
var scope = "Global Variable";
function checkScope() {
var scope = "Local Variable";
var f_lit = function() { //関数リテラル
return scope;
};
console.log(f_lit()); //結果:Local Variable
var f_con = new Function("return scope; "); //Functionコンスタラクター
console.log(f_con()); //結果:Global Variable
}
checkScope();
関数リテラルf_litもFunctionコンストラクターf_conも関数内部で定義されているが、ログに出力される変数はそれぞれ異なる。
Functionコンストラクターでは関数内部で定義しているにも関わらず、グローバル変数を参照している。
Functionコンストラクターでの配下の変数では、その宣言場所に関わらず常にグローバルスコープと見なされる。