news 2026/10/6 1:56:46

用 eval 构建算术表达式计算器:现代 JavaScript 中 eval 的用法与安全边界

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 eval 构建算术表达式计算器:现代 JavaScript 中 eval 的用法与安全边界
  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载

eval是 JavaScript 内置的"代码字符串执行器",在本仓库《现代 JavaScript 教程》的 eval-calculator 任务 中,它被用来把用户输入的一行算术表达式(如2*3+2)直接计算并返回结果。读完本文,你将掌握eval(code)的执行语义、它在词法环境与严格模式下的行为差异,以及如何用正则表达式把"任意代码执行"收窄为"仅限数字与运算符的安全计算器",并了解window.eval与new Function这两种现代替代方案。

任务背景:一个三行代码的计算器

仓库中的任务原文(见 task.md)非常直接:创建一个计算器,弹出提示框让用户输入一个算术表达式,然后返回计算结果,并且明确说明"无需检查表达式的正确性,直接求值并返回结果即可"。

官方给出的 解法 精简到极致:

let expr = prompt("Type an arithmetic expression?", '2*3+2'); alert( eval(expr) );

运行后,用户在prompt中输入2*3+2,eval立即把它当作代码求值,alert弹出8。这短短三行就是整个任务的核心:用eval把"字符串形式的数学表达式"变成"真实计算结果",省去了手写解析器、中缀转后缀等全部工作。

eval 的执行语义:为什么它能"算出"表达式

要理解上面的代码为何成立,需要先弄清eval的本质。本仓库的 《Eval: run a code string》章节 给出了权威定义:eval用于执行一段字符串形式的代码,语法为:

let result = eval(code);

以下几个特性直接决定了计算器能否工作:

1. 返回值为最后一条语句的执行结果。这正是eval('1+1')返回2的原因:

let value = eval('1+1'); alert(value); // 2
let value = eval('let i = 0; ++i'); alert(value); // 1

所以eval(expr)会把表达式字符串当作一条语句执行,并返回其值——算术表达式恰好满足这一语义。

2. 代码字符串长度不限。它可以是多行文本,可以包含函数声明、变量声明等,不只是简单表达式。这一点既是能力,也是风险来源。

3. 在当前词法环境中执行。eval 的代码能看到并操作外层变量:

let a = 1; function f() { let a = 2; eval('alert(a)'); // 2,看到的是函数内部的 a } f();

它甚至可以修改外层变量:

let x = 5; eval("x = 10"); alert(x); // 10,值被修改了

4. 严格模式下拥有独立的词法环境。在'use strict'中,eval 内部声明的函数和变量对外不可见(本教程的可运行示例默认启用严格模式):

eval("let x = 5; function f() {}"); alert(typeof x); // undefined,不存在该变量 // 函数 f 同样不可见

若没有use strict,eval 没有独立词法环境,x和f就会泄漏到外层。

安全隐患:用户输入的是"任意代码",不只是数字

解法文档紧接着给出了一个至关重要的警示:用户实际上可以输入任何文本或代码。把上面的计算器稍作变化,输入alert('hacked')、while(true){}或process之类的字符串,eval都会老老实实去执行——因为它在设计上就是"执行任意代码字符串"。

这正是社区流传"eval is evil"的原因,详见 eval 章节的 "Using eval" 小节:

  • 安全层面:eval把不可信输入变成代码执行点,是典型任意代码注入(code injection)入口。用户输入的任何字符串都会被当作程序运行。
  • 工程层面:eval 能访问外层局部变量,这会带来副作用——代码压缩工具(minifier)在压缩时会安全地把局部变量重命名为a、b等短名,但一旦存在eval,这些变量可能被 eval 内的代码字符串引用,minifier 就"不敢"对它们改名,从而损害压缩率。
  • 维护层面:在 eval 字符串内部使用外层局部变量,属于不良编程实践,会让代码更难维护、更难追踪。

因此现代 JavaScript 中几乎不再需要 eval:它曾是弱语言时代的"万能工具",如今基本都能被现代语言特性或 JavaScript Module 替代。

安全加固:用正则把表达式"圈"在算术范围内

解法文档给出的加固思路是:用正则表达式检查expr,使其只允许包含数字和运算符。也就是说,在把字符串交给eval之前,先用模式匹配把它的内容限制在0-9与算术运算符集合内,从源头排除函数调用、语句、标识符等可执行代码。

由于原文档只给出思路、未给出具体正则,下面给出一种与本仓库正则表达式章节配套的落地实现——白名单式校验,只放行数字、四则运算符、括号、小数点与空白:

let expr = prompt("Type an arithmetic expression?", '2*3+2'); if (!/^[0-9+\-*/().\s]+$/.test(expr)) { alert("Invalid expression: only digits and operators are allowed"); } else { alert(eval(expr)); }

逐段解读这个正则:

片段含义
^...$锚定整个字符串,要求从头到尾全部匹配,杜绝"部分匹配"漏网
[0-9+\-*/().\s]字符集:数字0-9、加号、减号(\-转义,避免被当作范围符)、乘号、除号、左右括号、小数点、空白
+字符集内至少出现一次(也可用*允许空串)

校验通过后,表达式里只可能是数字、运算符、括号和小数点,即使拼接成12*(3+4)/2这类组合,eval执行的也始终是纯算术代码,无法注入alert、while或标识符。需要说明的是,这只是一种合理实现建议:本任务并不要求校验表达式的"正确性"(如除零、括号不配对),只要求把输入收窄到算术语法,这与 task.md 的"无需检查正确性"的定位一致。

另一种等价的加固思路是黑名单式(拒绝[a-zA-Z]、=、;等可疑字符),但白名单更稳健,因为它只承认明确安全的字符集合。若对正则语法本身需要复习,可直接参考仓库的 正则表达式系列章节。

现代替代方案:window.eval 与 new Function

即便要使用 eval,eval 章节 也给出了两条规避"访问外层变量"副作用的安全用法:

方案一:如果 eval 的代码不使用外层变量,用window.eval(...)调用,让代码在全局作用域执行,与外层词法环境彻底隔离:

let x = 1; { let x = 5; window.eval('alert(x)'); // 1,读到的是全局变量 }

方案二:如果 eval 的代码需要局部数据,改用new Function并把数据作为参数显式传入:

let f = new Function('a', 'alert(a)'); f(5); // 5

new Function的完整机制见本仓库 《The "new Function" syntax》章节:它同样能从字符串创建函数,但其[[Environment]]指向全局词法环境而非当前词法环境,因此天然无法访问外层局部变量;把数据作为参数显式传递,架构上更清晰,也完全不受 minifier 改名的影响。

回到计算器场景,如果要计算的"表达式"来自不可信的服务器或远端,且需要传入上下文数据,更安全的做法是:

function createSafeCalc(a, b) { return new Function('a', 'b', 'return a + b'); } let sum = createSafeCalc(2, 3); alert(sum()); // 5 —— 通过参数显式传值,而非依赖 eval 读取外层变量

完整示例:从朴素版到安全版

将任务与解法、加固方案合并,得到一套完整的、可直接在本仓库环境运行的"安全算术计算器":

// 朴素版(仅演示 eval 的能力,不做任何防护) let expr1 = prompt("Type an arithmetic expression?", '2*3+2'); alert(eval(expr1)); // 任意代码都会被执行,危险! // 安全版:白名单正则 + eval let expr2 = prompt("Type an arithmetic expression?", '2*3+2'); if (!/^[0-9+\-*/().\s]+$/.test(expr2)) { alert("Invalid expression: only digits and operators are allowed"); } else { alert(eval(expr2)); }

关于算术运算符本身(+、-、*、/、%、**的优先级与结合规则),可回溯本仓库的 《Basic operators, maths》章节 作为前置知识。

总结

围绕 Eval-calculator 任务 与 官方解法,可以提炼出如下要点:

  • 任务本质:用eval(expr)把用户输入的算术表达式字符串直接求值,核心代码只有 prompt + eval + alert 三行。
  • eval 的行为:eval(code)执行代码字符串并返回最后一条语句的结果;代码在当前词法环境中执行、可读写外层变量;严格模式下拥有独立词法环境。
  • 核心教训:eval执行的是任意代码,用户输入必须经过白名单正则校验(仅允许数字与运算符)后才可求值。
  • 现代替代:在全局作用域求值用window.eval(code);需要传入外部数据时用new Function显式传参,见 new Function 章节。

eval是理解 JavaScript"字符串即代码"模型的最佳窗口,但生产代码中请记住本章的核心结论:很少用到、可访问外层变量、优先用window.eval或new Function替代——这也是本仓库 eval 章节 想传达的完整信息。

  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载
上一篇:Handy 离线语音转文字:3 步跑通,开口说话文字直接出现,音频不出电脑
下一篇:DBeaver连接配置加密算法:选择安全与性能平衡的加密方式

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 1:54:45

jq 数组切片 `[n:m]` 详解:轻松截取数组两端子集

文档教程知识库 【免费下载链接】til :memo: Today I Learned 项目地址: https://gitcode.com/gh_mirrors/ti/til 点击查看 免费下载 本篇指南聚焦于 TIL 仓库 jq/get-a-slice-of-the-ends-of-an-array.md 所讲解的 jq 数组切片语法 [n:m]:从通用形式出…

作者头像 李华
网站建设 2026/10/6 1:53:08

VueUse useQRCode 深度指南:在 Vue 3 中响应式生成二维码

前端 【免费下载链接】vueuse Collection of essential Vue Composition Utilities for Vue 3 项目地址: https://gitcode.com/gh_mirrors/vu/vueuse 点击查看 免费下载 useQRCode 是 VueUse 生态中 vueuse/integrations 包提供的集成函数,它把第三方二…

作者头像 李华
网站建设 2026/10/6 1:49:33

DLSS Swapper 完整指南:3 步替换游戏内 DLSS 版本,随时可回退

DLSS Swapper 完整指南:3 步替换游戏内 DLSS 版本,随时可回退 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 游戏还卡在旧版 DLSS,新 DLL 早已释出,厂商却迟迟不出补丁。…

作者头像 李华
网站建设 2026/10/6 1:48:51

JavaScript 中比较两个 Date 对象的相等性与大小

文档教程知识库 【免费下载链接】til :memo: Today I Learned 项目地址: https://gitcode.com/gh_mirrors/ti/til 点击查看 免费下载 在 JavaScript 里判断两个 Date 对象是否相等并不是一个直观的操作——即使两个对象在概念上(甚至打印出来&#xff0…

作者头像 李华