Skip to content

eval 与动态执行代码

eval() 会把字符串当作 JavaScript 代码执行。它的核心问题不是“能不能用”,而是“几乎总有更安全、更清晰的替代方案”。

eval 是什么

js
eval('console.log(1)')

上面这段代码会在运行时解析字符串,并执行其中的内容。

可以先记成一句话:eval 让“字符串”变成“代码”。

为什么它经常被明确禁止

主要有三类问题:

  • 安全风险高
  • 可维护性差
  • 性能和优化表现差

这也是很多项目直接启用 no-eval 规则的原因。

安全风险

如果传入的字符串来源不可信,就等于把执行权交给外部输入:

js
const code = location.search.slice(1)
eval(code)

这类写法很容易演变成代码注入、XSS、数据泄露或权限滥用。

可维护性问题

eval 会让代码关系变得不透明:

  • 依赖关系无法静态分析
  • 重构工具很难正确处理
  • 阅读时不容易确认实际执行内容
  • 调试和排查成本更高

看到字符串拼代码时,通常就已经在增加维护成本。

性能问题

eval 的代码要在运行时再解析一次,JavaScript 引擎也更难对这类代码做稳定优化。

所以它不只是“不优雅”,通常也“不划算”。

直接调用和间接调用

这是 eval 的一个常见细节。

直接调用:

js
function run() {
  let value = 1
  eval('value = 2')
  console.log(value) // 非严格模式下可能变成 2
}

间接调用:

js
const e = eval
e('var count = 1')

间接调用不会按“当前局部作用域”来理解,行为边界也更难把握。

严格模式下要更谨慎

严格模式会进一步限制 eval 的行为,但这并不意味着它就变安全了。

可以记住:

  • 严格模式不会让 eval 变成推荐方案
  • 它只是让部分作用域行为没那么混乱

什么时候最容易误用

常见误用包括:

  • eval 解析 JSON
  • 拼接字符串做动态调用
  • 从服务端拿一段字符串直接执行
  • 为了访问对象属性而写动态代码

这些场景通常都能用更普通的写法解决。

更常见的替代方案

解析数据

不要这样:

js
const data = eval('(' + jsonText + ')')

应改成:

js
const data = JSON.parse(jsonText)

动态调用逻辑

不要拼接代码:

js
eval(`${actionName}(1, 2)`)

更适合用映射表:

js
const actions = {
  add(a, b) {
    return a + b
  },
  multiply(a, b) {
    return a * b
  },
}

const result = actions[actionName](1, 2)

动态读取属性

不要这样:

js
eval(`user.${key}`)

应直接使用属性访问:

js
const value = user[key]

有没有可能必须使用

极少数场景里会见到:

  • REPL
  • 在线代码编辑器
  • 受控的脚本执行器
  • 明确隔离过的沙箱环境

但这类场景的重点通常不是“会用 eval”,而是“如何隔离执行环境、限制能力、记录审计”。

Function 构造器的关系

Function 构造器本质上也是动态执行代码的一种方式:

js
const sum = new Function('a', 'b', 'return a + b')

它和 eval 一样,不应因为语法不同就被当成安全替代品。

使用建议

  • 默认把 eval 视为应避免的能力。
  • 看到字符串拼代码时,先想对象映射、普通函数调用、JSON.parse() 或解析器方案。
  • 如果真的要动态执行代码,先解决隔离和输入可信度,而不是先写执行逻辑。
  • 在业务项目里,优先让 ESLint 直接禁止 eval

基于 MIT 许可发布