应禁用eval(),改用安全解析器或function构造器;手写解析器需分三步:正则提取、双栈按优先级计算、清空剩余运算符;数字转换推荐number()并校验nan。

怎么用 eval() 安全地算四则表达式
直接用 eval() 解析用户输入的算式最省事,但也是最大风险点——它会执行任意代码。真实场景里,你几乎不该让它碰原始输入。
- 只在输入已严格过滤后使用:比如正则提前筛掉
;、__、import、exec等字符或关键字 - 更稳妥的做法是禁用
eval(),改用Function构造器限制作用域:const calc = new Function('a', 'b', 'op', 'return a op b');但注意这只能处理单个二元运算,复杂表达式仍需解析 - 常见错误现象:
eval("2 + 3 * 4")得到 20(符合数学优先级),但eval("1 + 2 + 3")没问题,eval("1++")就报SyntaxError——说明它认的是 JS 语法,不是纯数学表达式
手写简易表达式解析器的关键三步
不依赖第三方库时,核心是把字符串拆成“数字+运算符”再按优先级合并。不用递归下降,也能靠两个栈搞定。
- 第一步:用正则提取所有数字和运算符,例如
"3 + 4 * 2"→ 数组["3", "+", "4", "*", "2"];注意负号要和减号区分,"-5 + 3"的开头-是符号,不是运算符 - 第二步:维护一个数字栈和一个运算符栈;遇到乘除,立刻弹出前一个数字和运算符计算,结果压回数字栈;加减则先入栈,等最后统一算
- 第三步:遍历完后,把剩余运算符逐个处理。漏掉这步会导致
"1 + 2 + 3"只算到1 + 2 = 3就停了,最后结果变成 3 而不是 6
parseFloat() 和 Number() 在数字转换时的区别
用户输 " 12.3abc " 或 "0x1F",两种方法行为完全不同,选错会悄悄引入 bug。
-
parseFloat(" 12.3abc ")返回12.3,它从左扫到第一个非法字符就停;Number(" 12.3abc ")返回NaN,要求整个字符串可转为有效数字 -
parseFloat("0x1F")返回0(因为x不合法),而Number("0x1F")返回31(支持十六进制) - 计算器场景建议用
Number(),配合isNaN()显式判断失败;若用parseFloat(),必须额外检查输入是否被截断,比如比对原字符串长度和转换后字符串长度
为什么别在按钮点击里反复绑定 onclick
每次点击都重新 element.onclick = handleCalc,表面能用,但会覆盖上一次绑定,导致历史事件监听器丢失,调试时找不到为什么某次计算没响应。
- 正确做法是只绑一次,在初始化时设置:
document.getElementById('calc-btn').onclick = handleCalc; - 如果逻辑需要动态更新(比如切换加减模式),不要重绑事件,而是改内部状态变量,让
handleCalc自己读取当前模式 - 容易踩的坑:在循环里给多个按钮绑事件时写成
for (let i = 0; i console.log(i); },结果全输出btns.length——这是闭包和变量提升的老问题,得用let声明循环变量,或用forEach
真正难的不是算对 2 + 2,而是当用户粘贴进 "1e5 + 0.1e-3 * 2" 或连续点 + 十几次时,你的解析逻辑是否还稳得住。边界情况比主流程更花时间。









