スコープ
ある変数のスコープとは、その変数にアクセスできるコード上の範囲を意味する。
var で宣言された変数は function scope であるのに対し、let や const で宣言された変数は block scope と呼ばれるスコープを持っている。
function scope の変数は、自分を含む一番内側の関数がスコープとなる。一方 block scope の変数は、自分が属する一番内側の関数orブロックがスコープとなる。ブロックとは { 処理;処理;... } のことである。
Lexical Environment (LE)
JavaScript の実行環境はプログラムを実行する上で「どの変数が何の値を持っているか」というテーブルのようなものを保持しながらコードを一行一行実行する。ただし、変数はスコープというものがあり、スコープの外からはアクセスすることはできない。また、変数を参照するとき、同じ名前の変数が複数あればより内側のスコープに存在するものが優先される(シャドウイング)。また、後で説明するがJavaScriptの関数はクロージャーという機能を持っている。
このような性質を実現するために、JavaScript ではテーブルのチェーン構造によって変数を管理している。このテーブル一つ一つを Lexical Environment と呼び、変数の名前と値のペアを格納する。
Execution Context Stack
Call Stack とも呼ばれるが、これは「今実行中の関数はどの関数から呼び出されて、その関数はどの関数から呼び出されて...」という履歴のようなものを格納するスタック構造である。そしてこのスタックに詰め込まれるのは Lexical Environment である1。
実行の流れ
例えば以下のコードを考えよう。
const x = 1
if (true) {
const y = x + 1
const z = f(y)
}
function f(x) {
return x * 2
}
まずは、トップレベル(一番外側のスコープ)の変数のための LE が作られる。画像右の白い四角が LE を表す。この時点では通常の変数に値は入っていない(初期化されていない)。しかし、関数宣言は hoisting (巻き上げ) が起こるので、既に変数 f の値(関数)は入っている。
1行目の右辺が計算され、x に代入される。
if の条件式が true なのでブロックの中に入り、新しい LE が作られる。
y の値が計算される。
次に、f 関数が呼ばれる。関数の中に入るときも LE が作られる。
緑の LE が 青の LE ではなく赤の LE につながっているが、これは関数 f の外側のスコープが赤の LE だからである。
実引数として渡した y の値 2 が、f の仮引数 x の値となる。
x * 2 を計算するときに x が参照されるが、このとき赤の x ではなく緑の x の値が使われることになる。どのように x を見つけているかというと、Call Stack の先頭(図では一番下)の LE からスタートして、矢印でつながったチェーンをたどって一番最初に見つかった変数が選ばれるのである。そのため、緑の LE をまず探して、そこになければ赤の LE を探すことになるが、今の場合緑の LE に x があるのでそれが選ばれる。変数のシャドーイングはこのようにして発生するのである。
よって x の値は 2 なので 4 が return される。
関数を抜け、緑の LE は Execution Stack から削除される。
戻り値 4 が z に代入される。
ブロックを抜け、赤の LE は Execution Stack から削除され、このまま実行が終了する。
クロージャー
クロージャーとは外側のスコープに存在する変数を参照する関数のことである。例えば下のコードでは increment 関数がクロージャーである。
const c1 = counter()
const c2 = counter()
console.log(c1())
console.log(c1())
console.log(c1())
console.log(c2())
console.log(c2())
function counter() {
let x = 0
function increment() {
x += 1
return x
}
return increment
}
実行すると以下が出力される。
1
2
3
1
2
counter 関数を呼び出すたびに、別々のカウンターが作成されていることが分かるだろう。つまり、c1 と c2 は別々の変数 x を持っている。これも、LE で説明することができる。
c1 と c2 が代入された時点では、LE と Call Stack は次のようになっている。c1 と c2 はどちらも increment 関数の関数オブジェクトだが、別々の LE(緑) を指していることが分かる。これは、関数が呼び出されるたびに LE が作られるからである。
c1 が実行されると、その関数オブジェクトが指している LE (左の緑の LE) の下に新しい LE が作られ、そこで increment 関数が実行される。
ここで x += 1 が実行されるわけだが、最初の例で説明したとおり、当然この x は左の緑の LE の x を指すことになる。これにより左の緑の LE の x が 1 になり、右の緑の LE は影響を受けない。つまり、独立した2つの状態変数となっている。
ところで、この2つの x は対応する increment 関数からしかアクセスできない、いわば「隠れた変数」となっている。最近になって JavaScript にプライベートメンバー変数の構文が追加されたが、それまではプライベート変数はクロージャーでしか実現できなかった。
-
仕様では Execution Context Stack に詰め込まれるのは Execution Context であるが、ここでは Execution Context と Lexical Environment を同一視している。 ↩










