この章では、ECMAScript言語仕様が変数をどのように扱うかを詳しく見ていきます。
環境とは、ECMAScript仕様が変数を管理するために使用するデータ構造です。これは、キーが変数名で、値がそれらの変数の値である辞書です。各スコープには、関連付けられた環境があります。環境は、変数に関連する以下の現象をサポートできる必要があります。
それぞれの現象について、例を用いて説明します。
最初に再帰に取り組みます。次のコードを考えてください。
function f(x) {
return x * 2;
}
function g(y) {
const tmp = y + 1;
return f(tmp);
}
assert.equal(g(3), 8);
各関数呼び出しに対して、呼び出された関数の変数(パラメータとローカル変数)用の新しい記憶領域が必要です。これは、いわゆる実行コンテキストのスタックによって管理されます。実行コンテキストは環境への参照です(この章の目的において)。環境自体はヒープ上に格納されます。これは、実行がスコープを離れた後も、環境が時々存続するためです(クロージャを検討する際に確認します)。したがって、それ自体はスタックによって管理できません。
コードを実行する間、次のポーズを行います。
function f(x) {
// Pause 3
return x * 2;
}
function g(y) {
const tmp = y + 1;
// Pause 2
return f(tmp);
}
// Pause 1
assert.equal(g(3), 8);
何が起こるかを示します。
ポーズ1 – `g()` を呼び出す前(図 1)。
ポーズ2 – `g()` を実行中(図 2)。
ポーズ3 – `f()` を実行中(図 3)。
残りの手順:`return` があるたびに、1つの実行コンテキストがスタックから削除されます。
ネストされたスコープが環境によってどのように実装されているかを調べるために、次のコードを使用します。
function f(x) {
function square() {
const result = x * x;
return result;
}
return square();
}
assert.equal(f(6), 36);
ここでは、3つのネストされたスコープがあります。最上位レベルのスコープ、`f()` のスコープ、`square()` のスコープです。観察事項
したがって、各スコープの環境は、`outer` というフィールドを介して周囲のスコープの環境を指します。変数の値を検索する場合、最初に現在の環境でその名前を検索し、次に外部環境で、次に外部環境の外部環境などで検索します。外部環境の全チェーンには、現在アクセスできるすべての変数が含まれています(シャドウイングされた変数を除く)。
関数呼び出しを行うと、新しい環境が作成されます。その環境の外部環境は、関数が作成された環境です。関数呼び出しによって作成された環境の `outer` フィールドを設定するのを助けるために、各関数は、その「誕生環境」を指す `[[Scope]]` という内部プロパティを持っています。
コードを実行している間にポーズする箇所を示します。
function f(x) {
function square() {
const result = x * x;
// Pause 3
return result;
}
// Pause 2
return square();
}
// Pause 1
assert.equal(f(6), 36);
何が起こるかを示します。
クロージャ を実装するために環境がどのように使用されているかを確認するために、次の例を使用します。
ここで何が起こっているのでしょうか?`add()` は、関数を返す関数です。B行でネストされた関数呼び出し `add(3)(1)` を行うと、最初の引数は `add()` 用で、2番目の引数はそれが返す関数用です。これは、A行で作成された関数が、そのスコープを離れるときにその誕生スコープへの接続を失わないためです。関連付けられた環境はその接続によって存続し、関数はその環境内の変数 `x` にアクセスできます(`x` は関数内でフリーです)。
このネストされた `add()` の呼び出し方法には利点があります。最初の関数呼び出しのみを行う場合、パラメータ `x` がすでに記入されている `add()` のバージョンが得られます。
2つのパラメータを持つ関数を、それぞれ1つのパラメータを持つ2つのネストされた関数に変換することを、カリー化といいます。`add()` はカリー化された関数です。
関数のいくつかのパラメータのみを入力することを、部分適用といいます(関数はまだ完全に適用されていません)。関数の `bind()` メソッド は部分適用を実行します。前の例では、関数がカリー化されている場合、部分適用が簡単であることがわかります。
次のコードを実行する際に、3つのポーズを行います。
function add(x) {
return (y) => {
// Pause 3: plus2(5)
return x + y;
}; // Pause 1: add(2)
}
const plus2 = add(2);
// Pause 2
assert.equal(plus2(5), 7);
何が起こるかを示します。