indirect eval 是什么?

indirect eval 是什么?

在一些跨平台或兼容旧代码的 JavaScript 项目中,你可能见过类似下面的写法:

1
2
const indirectEval = eval;
indirectEval('var global = globalThis');

乍一看,这段代码几乎什么都没变:indirectEval 不过是 eval 的一个别名而已,为什么执行效果却和直接 eval(...) 完全不同,甚至还能“偷偷”往全局作用域里塞变量?

这并不是某种 hack,而是 ECMAScript 规范中一个非常正式、但极易被误解的语义设计。本文将由浅入深,系统地解释:

  • 什么是 direct eval / indirect eval
  • 它们的语义差异来自哪里
  • 为什么“是不是 direct eval”取决于语法而不是值
  • 这种机制在工程中是如何被合理利用的

1. 从一个真实问题说起

在 Node.js 中,我们习惯使用 global 作为全局对象:

1
global.foo = 1;

但在浏览器或 ESM 环境中:

1
global.foo = 1; // ReferenceError: global is not defined

很多三方库(尤其是历史较久的库)直接读取裸标识符 global,而不是 globalThis.global。这意味着:

  • 仅仅设置 globalThis.global = ... 是不够的
  • 你必须让 global 成为一个真正的全局标识符绑定

于是就出现了类似这样的代码:

1
2
const indirectEval = eval;
indirectEval('if (typeof global === "undefined") { var global = globalThis; }');

这段代码在模块 / 严格模式下依然能生效,关键就在于:它触发的是“间接 eval”


2. eval 到底特殊在哪里?

在 JavaScript 中,eval 是一个非常特殊的内建函数。

它不是简单地“接收字符串并执行”,而是:

  • 可能在当前作用域执行(direct eval)
  • 也可能强制在全局作用域执行(indirect eval)

而决定这两种行为的,并不是你传入了什么,也不是 eval 这个函数的“值”,而是——

调用 eval 时的语法形态。


3. 什么是 Direct Eval?

当且仅当满足以下条件时,规范认为这是一次 direct eval

1
eval('code');

并且:

  • eval 是一个未被重写的原生标识符
  • 调用语法中,eval 直接作为 Identifier 出现

此时的特征是:

  • 代码在当前词法环境中执行
  • 在严格模式 / 模块中,var 不会创建全局绑定

例如:

1
2
3
4
'use strict';

eval('var x = 1');
console.log(x); // ReferenceError

4. 什么是 Indirect Eval?

只要不满足 direct eval 的语法条件,哪怕调用的函数值仍然是原生 eval,就会变成 indirect eval

最常见的几种形式:

1
2
3
4
5
6
const f = eval;
f('code');

(0, eval)('code');

window.eval('code');

它们的共同点是:

  • 调用表达式中不再直接出现 eval(...) 这个语法形态
  • 语法树里没有 IdentifierName 为 "eval" 的 CallExpression

此时规范强制规定:

代码必须在全局执行上下文中执行。

因此:

1
2
3
4
5
6
'use strict';

const indirect = eval;
indirect('var y = 2');

console.log(y); // 2

var y 成为了真正的全局变量。


5. 为什么“是否 direct eval”是语法问题?

这是很多人最困惑的一点。

直觉上我们会觉得:

indirectEval 的值不就是 eval 吗?那为什么行为会不一样?”

但在 ECMAScript 中:

  • 是否 direct eval 是在解析阶段就决定的
  • 引擎并不会在运行时去比较“这个函数值是不是原生 eval”

换句话说:

direct eval 是一个 语法特权,不是一个 函数特性

一旦你通过变量、属性、表达式间接调用它,这个特权就自动失效。


6. 规范视角下的直观理解

可以这样理解规范的设计意图:

  • direct eval 能访问并修改当前作用域,是极其危险的能力
  • 因此只允许在最“显眼”、最难被隐藏的写法中使用:eval(...)
  • 任何形式的包装、转发、别名,都会被视为不安全,从而退化为全局执行

这也是为什么很多安全工具、代码审计规则,只要看到 eval( 就直接报红


7. 回到最初的那段代码

1
2
3
4
5
6
7
const indirectEval = eval as unknown as (code: string) => unknown;

indirectEval(
'if (typeof global === "undefined") {\n' +
' var global = typeof globalThis !== "undefined" ? globalThis : this;\n' +
'}',
);

这段代码的关键点只有一个:

它确保使用的是 indirect eval。

从而保证:

  • 在模块 / 严格模式下依然能执行
  • var global 会创建真正的全局绑定
  • 老代码中的 global.xxx 不会抛 ReferenceError

这不是取巧,而是规范允许的、可移植的行为


8. 工程上的建议

最后给一些实践层面的建议:

  1. 永远避免业务代码中的 eval
  2. 如果必须使用(polyfill / runtime shim 场景):
    • 明确区分 direct / indirect eval
    • 在代码和注释中解释原因
  3. 新代码优先使用 globalThis
  4. 只有在“兼容旧生态”时,才考虑创建 global 标识符

9. 总结

  • eval 的行为差异不是由函数值决定的
  • 而是由调用时的语法结构决定的
  • const indirectEval = eval; indirectEval() 会触发全局执行
  • 这是 ECMAScript 规范中一个有意为之的设计

indirect eval 是什么?
https://www.hangyu.art/2025-12-14/IndirectEval是什么/
作者
徐航宇
发布于
2025年12月14日
许可协议