Wednesday, 12 August 2015

Javascript: LexicalEnvironment vs VariableEnvironment

One javascript feature I learned while working on POCL.

Consider:

function foo() {
    try {
        throw 'hello'
    } catch (e) {
        return function inner() {return e}
    }
}

foo()()
=> 'hello'

function foo2() {
    try {
        throw 'hello'
    } catch (e) {
        function inner() {return e}
        return inner;
    }
}

foo2()()
Uncaught ReferenceError: e is not defined

Why do the results differ?

In the first case INNER is a function expression, while in the second case INNER is a function declaration.

According to the standard:

Javascript execution context has two Lexical Environments. One is called LexicalEnvironment (he-he), and another is VariableEnvironment [§10.3].

The CATCH statement introduces new LexicalEnvironment where E is defined, and leaves the VariableEnvironment untouched.

Function expression receives current LexicalEnvironment as its scope, while function declaration receives current VariableEnvironment as its scope [§13], therefore E is invisible for the function declaration.

Why everything is so complicated, why two Lexical Environments exist?

My guess it's because we can call function before its declaration:

(function () {
    return inner();
    function inner() {return 1};
})()

=> 1

(function () {
    var x = inner();
    return x;
    function inner() {return 1};
})()

=> 1

(function () {
    var x = inner();
    try {
       throw 'error'
    } catch (unused) {
       function inner() {return 1};
    };
    return x;
})()

=> 1;

To support this behavior, the variable INNER is created and bound to the function object early, when control enters the surrounding function; before any CATCH is executed; and therefore it doesn't see the variable introduced by the CATCH clause.

Unlike normal variables, visible in the scope of the whole enclosing function, the variable introduced by CATCH is only visible inside the CATCH clause:

(function() {
    var x = 1;
    console.log(x);
    try {
        throw 2
    } catch (x) {
        console.log(x)
    }
    console.log(x)
})()

// log output:
1
2
1


That's why (I guess) javascript needs to distinguish LexicalEnvironment and VariableEnvironment.

During years of JS programming I never hit this difference in practice - never captured a catch variable by a closure. I only detected this strange thing when reading the standard in order to implement some code transformations needed for POCL, and was wondering, why does it need to distinguish LexicalEnvironment from VariableEnvironment.

BTW, another case when names are bound within a scope smaller that the enclosing function is the WITH statement. So it has the same problem regarding function expression / function declaration:

var o = {x: 1, y: 2}
(function() {with (o) {return function inner() {return x}}})()()
=> 1

(function() {with (o) {function inner() {return x}; return inner}})()()
Uncaught ReferenceError: x is not defined


No comments:

Post a Comment