indirect eval 是什么?
indirect eval 是什么?
在一些跨平台或兼容旧代码的 JavaScript 项目中,你可能见过类似下面的写法:
1 | |
乍一看,这段代码几乎什么都没变:indirectEval 不过是 eval 的一个别名而已,为什么执行效果却和直接 eval(...) 完全不同,甚至还能“偷偷”往全局作用域里塞变量?
这并不是某种 hack,而是 ECMAScript 规范中一个非常正式、但极易被误解的语义设计。本文将由浅入深,系统地解释:
- 什么是 direct eval / indirect eval
- 它们的语义差异来自哪里
- 为什么“是不是 direct eval”取决于语法而不是值
- 这种机制在工程中是如何被合理利用的
1. 从一个真实问题说起
在 Node.js 中,我们习惯使用 global 作为全局对象:
1 | |
但在浏览器或 ESM 环境中:
1 | |
很多三方库(尤其是历史较久的库)直接读取裸标识符 global,而不是 globalThis.global。这意味着:
- 仅仅设置
globalThis.global = ...是不够的 - 你必须让
global成为一个真正的全局标识符绑定
于是就出现了类似这样的代码:
1 | |
这段代码在模块 / 严格模式下依然能生效,关键就在于:它触发的是“间接 eval”。
2. eval 到底特殊在哪里?
在 JavaScript 中,eval 是一个非常特殊的内建函数。
它不是简单地“接收字符串并执行”,而是:
- 可能在当前作用域执行(direct eval)
- 也可能强制在全局作用域执行(indirect eval)
而决定这两种行为的,并不是你传入了什么,也不是 eval 这个函数的“值”,而是——
调用
eval时的语法形态。
3. 什么是 Direct Eval?
当且仅当满足以下条件时,规范认为这是一次 direct eval:
1 | |
并且:
eval是一个未被重写的原生标识符- 调用语法中,
eval直接作为Identifier出现
此时的特征是:
- 代码在当前词法环境中执行
- 在严格模式 / 模块中,
var不会创建全局绑定
例如:
1 | |
4. 什么是 Indirect Eval?
只要不满足 direct eval 的语法条件,哪怕调用的函数值仍然是原生 eval,就会变成 indirect eval。
最常见的几种形式:
1 | |
它们的共同点是:
- 调用表达式中不再直接出现
eval(...)这个语法形态 - 语法树里没有 IdentifierName 为
"eval"的 CallExpression
此时规范强制规定:
代码必须在全局执行上下文中执行。
因此:
1 | |
var y 成为了真正的全局变量。
5. 为什么“是否 direct eval”是语法问题?
这是很多人最困惑的一点。
直觉上我们会觉得:
“
indirectEval的值不就是eval吗?那为什么行为会不一样?”
但在 ECMAScript 中:
- 是否 direct eval 是在解析阶段就决定的
- 引擎并不会在运行时去比较“这个函数值是不是原生 eval”
换句话说:
direct eval 是一个 语法特权,不是一个 函数特性。
一旦你通过变量、属性、表达式间接调用它,这个特权就自动失效。
6. 规范视角下的直观理解
可以这样理解规范的设计意图:
- direct eval 能访问并修改当前作用域,是极其危险的能力
- 因此只允许在最“显眼”、最难被隐藏的写法中使用:
eval(...) - 任何形式的包装、转发、别名,都会被视为不安全,从而退化为全局执行
这也是为什么很多安全工具、代码审计规则,只要看到 eval( 就直接报红。
7. 回到最初的那段代码
1 | |
这段代码的关键点只有一个:
它确保使用的是 indirect eval。
从而保证:
- 在模块 / 严格模式下依然能执行
var global会创建真正的全局绑定- 老代码中的
global.xxx不会抛ReferenceError
这不是取巧,而是规范允许的、可移植的行为。
8. 工程上的建议
最后给一些实践层面的建议:
- 永远避免业务代码中的 eval
- 如果必须使用(polyfill / runtime shim 场景):
- 明确区分 direct / indirect eval
- 在代码和注释中解释原因
- 新代码优先使用
globalThis - 只有在“兼容旧生态”时,才考虑创建
global标识符
9. 总结
eval的行为差异不是由函数值决定的- 而是由调用时的语法结构决定的
const indirectEval = eval; indirectEval()会触发全局执行- 这是 ECMAScript 规范中一个有意为之的设计