ECMAScript のレキシカル環境 〜catch 節のスコープのお話〜

Twitter で ECMAScript (JavaScript) の catch 節のスコープについての話をみかけた ので、ちょっと調べてみた。

catch( err )err のスコープは?

例外処理の機構で使用される catch( err ) ですが、この err はどの範囲で有効なのか? 普通に考えると catch 節内だけで有効な気がするけど、ECMAScript では一般に関数ごとにスコープを持つと言うし、関数全体? まさかグローバル?

(function test() {
    try {
        throw 0;
    } catch( err ) {
        print( err ); // 0
    }
    print( err ); // [ERROR] err is not defined
})();

試したところ、どうやら catch 節内だけのスコープがある模様。

ECMA-262 (5th edition) の 10.2 節 (Lexical Environments) のところには以下のように書かれていて、catch 節にもレキシカル環境 (Lexical Environment) が結び付けられていることがわかります。

Usually a Lexical Environment is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a WithStatement, or a Catch clause of a TryStatement and a new Lexical Environment is created each time such code is evaluated.

catch 節内の VariableDeclaration は?

じゃあ catch 節内の VariableDeclaration ((var variable_name; という形式の変数宣言)) は catch 節の外の環境には影響を与えないのか?

(function test2() {
    try {
        throw 1;
    } catch( err ) {
        var n = err;
    }
    print( n ); // 1
})();

試してみると、catch 節内の var で宣言された変数は、catch 節の外でも有効でした。 なぜ?

レキシカル環境と実行コンテキスト

ECMA-262 (5th edition) の 10.3 節 (Execution Context) を見ると、次のように書いてあります。

When control is transferred to ECMAScript executable code, control is entering an execution context. Active execution contexts logically form a stack. The top execution context on this logical stack is the running execution context. A new execution context is created whenever control is transferred from the executable code associated with the currently running execution context to executable code that is not associated with that execution context. The newly created execution context is pushed onto the stack and becomes the running execution context.


An execution context contains whatever state is necessary to track the execution progress of its associated code. In addition, each execution context has the state components listed in Table 19.


The LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments. When an execution context is created its LexicalEnvironment and VariableEnvironment components initially have the same value. The value of the VariableEnvironment component never changes while the value of the LexicalEnvironment component may change during execution of code within an execution context.

Table 19 の内容は次のとおり。

Component Purpose
LexicalEnvironment Identifies the Lexical Environment used to resolve identifier references made by code within this execution context.
VariableEnvironment Identifies the Lexical Environment whose environment record holds bindings created by VariableStatements and FunctionDeclarations within this execution context.
ThisBinding The value associated with the this keyword within ECMAScript code associated with this execution context.

適当にまとめると、『ECMAScript を実行するための実行コンテキストは LexicalEnvironment と VariableEnvironment と ThisBinding から成る。 LexicalEnvironment と VariableEnvironment は常にレキシカル環境である。 実行コンテキストが生成されるとき、最初は LexicalEnvironment と VariableEnvironment は同じ値を持つ。 VariableEnvironment の値は決して変更されないが、LexicalEnvironment の値は変更される可能性がある』 という感じでしょうか。 つまり、実行コンテキストにおけるレキシカル環境として、変更される可能性がある LexicalEnvironment と変更されない VariableEnvironment の 2 種類のレキシカル環境がある、というわけです。

実行コンテキストは関数へ入るときに生成され、catch 節に入るときには LexicalEnvironment が変更される

さて、実行コンテキストには LexicalEnvironment と VariableEnvironment の 2 種類のレキシカル環境があることがわかりましたが、それぞれどのように使われるのでしょうか。

実行コンテキストが生成されるのはいつかというと、ECMA-262 の 10.4 節 (Establishing an Execution Context) に書かれています。 適当に意訳すると 『グローバルコードや eval 関数で使われているコードを評価する際、および関数を呼び出す際に新しい実行コンテキストが作られ、そこに入る。 実行コンテキストに入る際、ThisBinding がセットされ、VariableEnvironment と初期の LexicalEnvironment が定義される。 また、宣言束縛の具現化 (10.5 節) が実行される。』 と書かれています。 宣言束縛の具現化 (Declaration Binding Instantiation) というのが、関数宣言 (FunctionDeclaration) や変数宣言 (VariableDeclaration) を環境に結びつけることを意味しています。

つまり、VariableDeclaration が環境に結び付けられるのは実行コンテキストが生成されるときであり、実行コンテキストが生成されるのは関数を呼び出すときである、ということです。 すなわち、catch 節の中に VariableDeclaration があったとしても、それが環境に結び付けられるのはその catch 節を含む関数 (またはグローバルコードか eval コード) が呼び出されるときであって、catch 節に入るときはない、というわけです。

では catch 節に入るときはどのようなことが行われているのでしょうか? これは、12.14 節 (The try Statement) に書かれています。

The production Catch : catch ( Identifier ) Block is evaluated as follows:


1. Let C be the parameter that has been passed to this production.
2. Let oldEnv be the running execution context’s LexicalEnvironment.
3. Let catchEnv be the result of calling NewDeclarativeEnvironment passing oldEnv as the argument.
4. Call the CreateMutableBinding concrete method of catchEnv passing the Identifier String value as the argument.
5. Call the SetMutableBinding concrete method of catchEnv passing the Identifier, C, and false as arguments. Note that the last argument is immaterial in this situation.
6. Set the running execution context’s LexicalEnvironment to catchEnv.
7. Let B be the result of evaluating Block.
8. Set the running execution context’s LexicalEnvironment to oldEnv.
9. Return B.


NOTE No matter how control leaves the Block the LexicalEnvironment is always restored to its former state.

いくつか処理を行っていますが、要は実行コンテキストの LexicalEnvironment を変更しているわけです。 その新しい LexicalEnvironment は、古い LexicalEnvironment を親環境とし、catch 節にパラメータとして渡された値を唯一の変数束縛として持っています。

識別子の名前解決に使われるのは LexicalEnvironment (10.3.1 節 Identifier Resolution 参照) なので、catch 節に渡されたパラメータは catch 節の中だけのスコープを持つわけです。 ちなみに with 文も catch 節と同じで、実行時に LexicalEnvironment を変更します。

まとめ

  • グローバルコードと eval コードの評価時、および関数の呼び出し時に新たな実行コンテキストを生成し、そこに入る
  • 実行コンテキストは ThisBinding と LexicalEnvironment と VariableEnvironment から成る
  • 識別子の名前解決に使われるのは LexicalEnvironment
  • 実行コンテキストの生成時に、関数宣言と変数宣言は環境に結び付けられる (このとき LexicalEnvironment と VariableEnvironment は同じ値)
  • with 文と catch 節は LexicalEnvironment を変更する