The following excerpts are taken from ECMA-262 7th edition (ECMAScript 2016) – section numbers sometimes differ between versions.
18.2.1.1 Runtime Semantics: PerformEval( x, evalRealm, strictCaller, direct)
…
9. If direct is true, then
a. Let lexEnv be NewDeclarativeEnvironment(ctx’s LexicalEnvironment).
b. Let varEnv be ctx’s VariableEnvironment.
10. Else,
a. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]).
b. Let varEnv be evalRealm.[[GlobalEnv]].
So for an indirect call (not calling eval
by name) you arrive at step 10 which calls NewDeclarativeEnvironment(E)
with the global environment record as parameter value.
NewDeclarativeEnvironment
is described in section 8.1.2.2. As expected this creates an environment record for any variables defined using let
within script parsed by eval
, and sets the “outer lexical environment reference” of that record to the environment record provided as argument.
Step 10.b sets the variable environment record, for named functions and variables declared with var
, to the global environment record of the realm eval
was called in – meaning window
in a browser document.
In short, calling eval
indirectly creates a separate environment record for let
variables declared within the code being evaluated, whose next outer scope (lexical reference) is the global object, but uses the global object for var
and named function declarations.
If you want to have evaluated code inherit the scope of surrounding code, make a direct call using the name eval
as the function reference.
The presence of both 9.a and 10.a means that variables declared with let
are not retained after a call to eval
no matter what the type of call..
The Historical Why (edit)
The behavior of indirect calls is likely the result of deprecating calling eval
on an object and removing the eval
property from Object.prototype
.
From the JavaScript 1.2 reference for Object data type methods:
eval Evaluates a string of JavaScript code in the context of the specified object.
and calling eval
on an object in such early versions of JavaScript would result in code evaluation as if it were inside a with (object) {}
statement.
The rules for indirect calls replace this behavior with a standard response: if an object method is set to eval
the previous behavior of object.eval
is not recreated and the scope of evaluated code is the global object exceot for let
variables. This is similar to the way creating a function from text with new Function
behaves as if it were defined in global scope. It also has the effect that the this
value seen by indirect calls to eval
is the global object (this
values reside in environmental records).
solved Javascript scoping change