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