- 文档/教程
- 前端
【免费下载链接】en.javascript.info
Modern JavaScript Tutorial
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); // 2let 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); // 5new 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
相关推荐
Alpine 的 CSP 兼容构建(@alpinejs/csp):在无 unsafe-eval 环境下安全使用表达式
Alpine 的 CSP 兼容构建(@alpinejs/csp):在无 unsafe eval 环境下安全使用表达式 导读 本文聚焦 Alpine 官方提供的
前端JavaScript 表达式求值器:更安全的数学计算神器
JavaScript 表达式求值器:更安全的数学计算神器 在这个高度数字化的时代,JavaScript 已经成为网页开发和服务器端编程的重要语言,而数学计算则是
使用 ANTLR4 构建算术表达式语法:arithmetic 文法全面解析
使用 ANTLR4 构建算术表达式语法:arithmetic 文法全面解析 导读 本文以 grammars v4 仓库中的 arithmetic 文法 http
编程语言编译器开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考