☰
基于 eval 的表达式计算器(Eval-calculator):Modern JavaScript Tutorial 实战解析
2026/10/6 1:52:44 网站建设 项目流程
  • 文档/教程
  • 前端

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

Modern JavaScript Tutorial

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

eval是 JavaScript 内置的“代码字符串执行器”,本教程(Modern JavaScript Tutorial)在 1-js/99-js-misc/02-eval/article.md 一章中系统讲解了它的执行语义与安全边界,并以 “Eval-calculator” 任务作为实战收尾:用prompt读取一条算术表达式,用eval求值并返回结果。读完本文,你将掌握eval的词法环境规则、严格模式差异,并能独立实现一个最小可用的表达式计算器,同时学会用正则校验等手段规避代码注入风险。

任务背景:Eval-calculator 在教程中的位置

Eval-calculator 是 eval 章节(1-js/99-js-misc/02-eval)配套的练习任务,任务描述位于 1-js/99-js-misc/02-eval/1-eval-calculator/task.md,要点如下:

  • 创建一个计算器:用prompt提示用户输入一条算术表达式,然后返回计算结果;
  • 明确约定:本任务不要求校验表达式的正确性,直接求值并返回结果即可;
  • 任务附带[demo]演示位,完整的可运行参考实现在同目录的 solution.md。

它被设计为“入门难度”(importance: 4),目的不是展示复杂语法,而是让你亲手体验eval最自然的用途——把运行时才得到的代码字符串变成真正的执行结果。

前置知识:eval 的核心执行语义

在动手写计算器之前,先明确eval的四条关键语义(均来自 eval 章节正文 article.md)。

1. 语法与返回值

let result = eval(code);
  • code是任意 JavaScript 代码字符串,可以很长,可以包含换行、函数声明、变量等;
  • 返回值是最后一条语句的结果。
let value = eval('1+1'); alert(value); // 2
let value = eval('let i = 0; ++i'); alert(value); // 1

这正是计算器任务的基石:2*3+2这类算术字符串传入eval,会按 JavaScript 运算符优先级求出数值结果。

2. 在当前词法环境中执行

eval的代码在调用处的词法环境中执行,因此它可以看到(并修改)外部变量:

let a = 1; function f() { let a = 2; eval('alert(a)'); // 2,看到的是 f 内部的 a } f();
let x = 5; eval("x = 10"); alert(x); // 10,外部变量被修改

3. 严格模式下拥有独立词法环境

在'use strict'下,eval拥有自己的词法环境,内部声明的函数和变量对外不可见:

// 注意:本仓库的可运行示例默认开启 'use strict' eval("let x = 5; function f() {}"); alert(typeof x); // undefined(不存在该变量) // 函数 f 同样不可见

而非严格模式下,eval没有独立词法环境,x和f会在外部泄漏可见。

4. “eval is evil”:为什么现代代码几乎不用它

正文明确指出:在现代化的 JavaScript 中,eval的使用极少。原因包括:

  • 早年 JS 语言能力弱,很多事只能靠eval完成,但这个时代早已过去;
  • 能访问外部变量这一特性带来副作用与维护困难;
  • 代码压缩器(minifier)会重命名局部变量以减小体积,但若存在eval,被 eval 字符串可能访问的变量就不能安全重命名,从而拉低压缩率。

正文给出的建议是:绝大多数eval场景都可以用现代语言结构或 JavaScript Module 替代。以上语义与安全边界,是理解并扩展计算器任务的前提。

参考答案:最小可用的 eval 计算器

任务的参考实现非常精简,完整代码位于 solution.md:

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

逐行拆解:

  1. prompt("Type an arithmetic expression?", '2*3+2'):弹出输入框,默认值预填2*3+2,用户输入如1+2*3、(10-4)/2之类的算术表达式;
  2. eval(expr):把用户输入的字符串当作 JavaScript 代码求值。由于算术表达式本身就是合法的 JS 表达式,eval会按运算符优先级正确计算;
  3. alert(...):把eval返回的“最后一条语句的结果”弹出显示。

该实现可以直接复制到浏览器控制台或 HTML<script>中运行,配合prompt/alert即可构成一个完整的交互式计算器 demo。

任务边界与风险:为什么“不校验”是刻意的

任务描述特意强调“无需校验表达式正确性”,这不是疏漏,而是教学策略——先用最直接的方式展示eval的价值,把安全话题留到下一步。而参考答案随后明确点出了隐患:

用户可以输入任意文本或任意代码。

例如用户输入alert('hacked')、x = 100甚至while(true){},eval都会忠实地执行它们。这意味着:

  • 代码注入:表达式输入框实际上成了一个任意 JS 代码执行入口;
  • 外部变量污染:由于eval在当前词法环境执行,它可以读写外层变量;
  • 性能与稳定性风险:恶意或误输入的代码可能引发死循环、报错中断等。

这正是 eval 章节正文强调“evil”的现实体现。因此参考答案给出了加固方向(见下文),这也构成了任务的“进阶思考题”。

安全化改造:用正则把输入限制为纯算术

参考答案提出的安全策略是:用正则表达式校验expr,只允许包含数字与运算符。教程中正则相关内容位于 9-regular-expressions/index.md 章节,可结合学习。

一个可行的加固版实现思路如下(在原答案基础上扩展):

// 只允许数字、空格与算术运算符:+ - * / ( ) .(含小数点和科学计数可选) let expr = prompt("Type an arithmetic expression?", '2*3+2'); if (/^[0-9+\-*/().\s]+$/.test(expr)) { alert( eval(expr) ); } else { alert("Only arithmetic expressions are allowed!"); }

要点说明:

  • 正则^[0-9+\-*/().\s]+$用^与$锚定整个字符串,从字符层面白名单化:只接受数字、四则运算符、括号、小数点与空白;
  • 经过校验后,字符串中不可能再携带alert(...)、=、引号、分号等代码语法成分,从而把eval的执行范围收窄到纯算术求值;
  • 注意这是“字符级”校验,仍可能放行如1/0(结果为Infinity)、0*999...等边界输入,但它已足以阻断绝大多数注入场景。

如果想彻底避开eval,教程正文还提供了两条更安全的路径(详见下一节)。

两条更安全的替代路径

eval 章节正文给出了两条规避局部变量访问问题的标准做法,均可在计算器场景中借鉴。

路径一:window.eval(...)—— 把执行推到全局作用域

如果被求值的代码不需要访问外部局部变量,用window.eval调用,代码将在全局作用域执行:

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

此时eval只能看到全局变量,看不到当前块级作用域里的let x = 5,副作用面大幅收窄。

路径二:new Function—— 显式传参,杜绝闭包依赖

如果被求值的代码确实需要外部数据,教程推荐改用new Function并显式传参。new Function的完整语法与闭包行为见 1-js/06-advanced-functions/07-new-function/article.md:

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

关键原理:用new Function创建的函数,其内部属性[[Environment]]指向全局词法环境而非创建处的环境,因此它天然看不到外部局部变量,数据只能通过参数显式传入。这比依赖eval隐式读取外层变量清晰得多,也便于维护。

计算器场景的最终形态

把两条路径结合起来,一个“更安全”的计算器可以这样写:

let expr = prompt("Type an arithmetic expression?", '2*3+2'); // 第一步:正则白名单,只放行纯算术字符 if (!/^[0-9+\-*/().\s]+$/.test(expr)) { alert("Only arithmetic expressions are allowed!"); } else { // 第二步:在全局作用域求值,避免接触局部变量 alert( window.eval(expr) ); }

(若你的页面运行在 Worker 等无window的环境,可改用globalThis.eval(expr)。)

实战要点速览

  • 核心一行:eval(prompt("Type an arithmetic expression?", '2*3+2'))即是最小可用的表达式计算器,直接体现“eval返回最后一条语句结果”的语义;
  • 词法环境:非严格模式下eval可读写调用处的外部变量,严格模式下拥有独立词法环境、内部声明不外泄;
  • 风险边界:不对输入做任何校验的计算器等同于任意代码执行入口,必须警惕注入;
  • 加固手段:正则白名单校验输入字符(参考 9-regular-expressions);需要全局作用域时用window.eval;需要外部数据时改用new Function显式传参(参考 new Function 章节);
  • 工程态度:如 eval 章节正文 所总结——现代 JavaScript 中eval几乎无必要,优先用模块(1-js/13-modules/index.md)与现代语言结构替代;仅在确实需要运行时动态执行代码字符串的极少数场景(如接收服务器端代码、模板动态编译)才考虑它。

以上即为 Eval-calculator 任务的完整解析:从任务描述(task.md)、参考实现(solution.md)到底层语义与安全加固,你可以按此路径在本地直接复现并进一步实验。

  • 文档/教程
  • 前端

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

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载
上一篇:StartBootstrap-Resume构建系统详解:自动化工作流与最佳实践
下一篇:5分钟上手!dex2jar与IDEA反编译插件无缝集成指南

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询