global.eval 无法访问词法范围内的变量。该行为是否符合 ECMAScript 标准?

global.eval is not able to visit variables in the lexical scope. Does the behavior comply ECMAScript standard?

我有一个 JavaScript 文件,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"));

当我用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

因此,global.eval 适用于数学运算符,但无法访问变量 i,而 eval 适用于这两种情况。

这种行为是 V8 的限制吗?或者它是符合 ECMAScript 标准的预期行为吗?

是的,这是符合规范的行为。 ES5 15.1.2.1.1, Direct Call to Eval,表示调用 eval 为 "direct" 的一个要求是对 eval 的引用“ 具有环境记录作为其基值。”这意味着它不能是通过 属性 访问完成的引用(在这种情况下,拥有对象将是基值);它必须是一个 "bare" 函数。

这个区别对于 10.4.2, Entering Eval Code 的第 1 步至关重要:

  1. 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.

因此,对eval的间接调用被赋予全局变量环境,而不是局部变量环境。只有直接调用才能访问本地环境。

这样做是出于实际实施原因,因为 eval 可以向垃圾收集器发出信号,表明需要避免清理任何变量。例如,这是没有 eval:

的情况
function foo() {
    var a = 5, b = 6, c = 7;
    return function() { return a; }
}
var func = foo();
alert(func());

foo 返回的函数可能会在 foo 终止后访问 a,但我们可以确定 bc 永远不会被访问foo 终止后再次。 bc 可以安全地进行垃圾回收,而 a 仍未回收。

现在 eval:

function foo() {
    var a = 5, b = 6, c = 7;
    return function(exp) { return eval(exp); }
}
var func = foo();
alert(func("b"));

通常无法确定 eval 表达式 exp 是否会引用给定的变量,因此垃圾收集器必须 从不 收集任何变量,以便它们仍然可供返回的函数使用。

为了确定 eval 正在使用,解析器必须能够可靠地识别对 eval 的调用。如果 eval 以间接方式呈现,如 global["e"+"va"+"l!"[0]],规范说明 evaled 代码无法访问 any局部变量,从而避免垃圾回收问题。