I have a JavaScript file, e.js
var global = Function('return this')();
var i = 1;
console.log(eval("100-1"));
console.log(eval("i"));
console.log(global.eval("100-1"));
console.log(global.eval("i"));
When I execute it by V8:
$ node e.js
99
1
99
undefined:1
i
^
ReferenceError: i is not defined
at eval (eval at <anonymous> (/private/tmp/xxxx/e.js:8:20), <anonymous>:1:1)
at eval (native)
at Object.<anonymous> (/private/tmp/xxxx/e.js:8:20)
at Module._compile (module.js:456:26)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:902:3
As a result, global.eval
works for math operator, but it is unable to visit the variable i
, while eval
works for both cases.
Is this behavior a limitation of V8? Or is it the expected behavior according to ECMAScript standard?
Yes, this is spec-compliant behavior. ES5 15.1.2.1.1, Direct Call to Eval, says that one requirement for a call to eval
to be "direct" is that the reference to eval
"has an environment record as its base value." This means it cannot be a reference done by property access (in which case the owning object would the base value); it must be a "bare" function.
This distinction is critical to step 1 of 10.4.2, Entering Eval Code:
- If there is no calling context or if the eval code is not being evaluated by a direct call (15.1.2.1.1) to the eval function then,
- a. Initialise the execution context as if it was a global execution context using the eval code as C as described in 10.4.1.1.
Thus, an indirect call to eval
is given a global variable environment, not the local variable environment. Only direct calls get access to the local environment.
This is done for practical implementation reasons, because eval
can signal to garbage collectors a need to avoid cleaning up any variables. For example, here's a case without eval
:
function foo() {
var a = 5, b = 6, c = 7;
return function() { return a; }
}
var func = foo();
alert(func());
The function returned by foo
might access a
after foo
terminates, but we can be sure b
and c
will never be accessed ever again after foo
terminates. b
and c
can be safely garbage collected, while a
remains uncollected.
Now a case with eval
:
function foo() {
var a = 5, b = 6, c = 7;
return function(exp) { return eval(exp); }
}
var func = foo();
alert(func("b"));
It's impossible to generally decide if the eval
expression exp
will refer to a given variable, so the garbage collector must never collect any of the variables so they are still available for use by the returned function.
In order to decide that eval
is in use, the parser must be able to reliably recognize a call to eval
. If the eval
is presented in an indirect way, like global["e"+"va"+"l!"[0]]
, the spec says that that eval
ed code doesn't get access to any local variables, thereby avoiding the garbage collection problem.