前回、JavaScriptの変数の挙動について、細かい検証も含めて改めて見直しました。
JavaScriptの変数の挙動について検証してやんよ!!!(var命令編)
今回はその流れでスコープとクロージャについても掘り下げて、よりJavaScriptの知識を深めていくんだ・・・
「オレ、この戦いが終わったらアイツと結婚するんだ・・・」
スコープについてのおさらい
グローバルスコープ
- ・トップレベルではvarを付けても付けなくてもグローバル変数扱いになる
- ・関数内でvar命令を使わずに宣言された変数はグローバル変数となる
ローカルスコープ
- ・関数内でvarを付けて変数を宣言すると、宣言した関数の外では呼び出せない
- ・varで宣言した変数は関数のどこで宣言されていようと常に関数内だけの変数と見なされる
- ・varで宣言した変数は関数内のどこであっても、宣言さえされていればその変数が有効になる
その他の特長
- ・JavaやC#、VisualBasiなどの言語では、if や for などの {} で囲まれたブロックごとのスコープが存在しないJavaScript特有の仕様
- ・ローカル変数の記憶領域は、関数の実行が終わり次第、破棄される
- ・グローバル変数はプログラムが終了するまで記憶領域を確保するため、関数内でしか必要ない変数までグローバル変数にした場合、無駄にメモリを消費することになる
- ・varによる変数宣言は何度でも行える(下記コード参照)
var name "yanyo";
var name;
console.log(name); // yanyo
仮引数のスコープ(基本型)
仮引数とは
呼び出し元から関数に対して渡されたパラメータを受け取る変数のことです。
仮引数は、基本的にローカル変数として処理されます。
var value = 10; // グローバル変数に10を代入
function funcSubtraction(value) {
value--; // 値から1をマイナス
return value;
}
// ※1 : 99(仮引数の100から1マイナスされる)
console.log("※1 : " + funcSubtraction(100));
// ※2 : 10
console.log("※2 : " + value);
トップレベルのグローバル変数「value」に10の値がセットされていますが、
funcSubtraction関数の引数には仮引数の「value」はローカル変数と見なされるため、この仮引数をいくら操作しても
グローバル変数の「value」には影響がでません。
従って、funcSubtraction関数で仮引数に100を渡してもグ口ーバル変数「value」が書き換えられることはなく、※2ではもともとの値である10がそのまま返されます。
参照型としての仮引数
仮引数に渡される値が
参照型(値を格納したメモリ上のアドレスだけを格納している型)となった場合では、基本型の出力とは異なってしまいます。
var value = [1, 2, 3, 4, 5];
function delectedElement(value) {
value.pop();
return value;
}
console.log("※1: " + delectedElement(value)); // ※1: 1, 2, 3, 4
console.log("※2: " + value); // ※2: 1, 2, 3, 4
参照型の値を受け渡しする場合には、
渡される値も(値そのものではなく)メモリ上のアドレス情報だけとなります。このような値の渡し方を
参照渡しと言います。
定義されたグローバル変数「value」とdelectedElement関数内で定義された仮引数(ローカル変数)とは変数としては別物ですが、
※1でグ口ーバル変数「value」の値が仮引数「value」に渡された時点で、参照しているものはpop関数で末尾を削られた引数「value」なので結果的には同じメモリなわけです。
したがって、delectedElement関数の中で配列を操作した場合、その結果はグローパル変数「value」にも反映されることになります。
スコープチェーン
変数の参照をする手順として、最初に自スコープ内の
アクティベーションオブジェクト(関数の呼び出しが発生した際に自動的に生成されるオブジェクト。thisとか引数に該当)
を検索します。
そこで
該当する変数が見つからなかった場合に1つ外側のスコープにあるアクティベーションオブジェクトから対象の変数を検索。また該当する変数が
見つからなかった場合はもうひとつ外側へ・・・
といった様に処理に
必要なアクティベーションオブジェクトをどんどん外側へ探しに行く過程がスコープを鎖のように繋ぎ渡り、各スコープをチェーンの一つ一つにたとえて
スコープチェーンと言います。
スコープチェーン【例題】
var text3 = 'やんよ'; // 3番目に検索される
var func1 = function(){
var text2 = 'WEB'; // 2番目に検索される
var func2 = function(){
var text1 = 'ときどき'; // 1番目に検索される
console.log(text1); // ① ときどき
console.log(text2); // ② WEB(該当する変数が無いためスコープ外に検索)
console.log(text3); // ③ やんよ(親スコープに該当する変数が無いためグローバル変数から参照)
}();
};
func1();
スクリプト処理概要
- ・まずスクリプトが動作した時点でグローバル変数「text3」に"やん"が初期化されます。
- ・変数「func1」には関数が定義されており、それがスクリプト下部で命令されます。
- ・変数「func1」から呼び出された無記名関数内で変数「text2」に"WEB"が初期化されます。
- ・次に変数「func2」にはさらにCall関数が定義されているため命令
- ・Call関数内で変数「text1」に"ときどき"が初期化されます。
- ・最後に「text1」「text2」「text3」の各変数をログ出力させます
ここまでがおおまかなスクリプトの流れ
スコープチェーンの順路
- ・文字列"ときどき"が定義された変数「text1」は自スコープ内にあるためそのままログ出力
- ・続いて変数「text2」をログ出力したいが、スコープ内に見当たらないので外側の親スコープ内を検索
- ・そこで文字列"WEB"が定義された変数「text2」がヒットしたのでログ出力
- ・最後に変数「text3」をログ出力させるためにさらに外側を検索
- ・例題のスクリプトではそれ以上の外側のスコープが見当たらないため、文字列"やん"が定義されたグローバル変数「text3」を見つけ出しログ出力
という様な流れでスクリプト内を検索していく働きがスコープチェーンと呼ばれます。
今回はスコープについて掘り下げましたが、最終的には動けばOKなコードを書いてるだけではなかなか意識することの無い領域だったので勉強になりました。
「オレたちの戦いはこれからだッ!!!」