不使用eval()实现JavaScript计算器是否可行?
不用eval/Function实现JavaScript计算器的函数式方案
不用eval()或Function()构造函数是更安全、更符合工程规范的做法,用函数式编程思路可以实现一个健壮的计算器,核心是通过词法分析→表达式转换→结果计算三个纯函数步骤完成,全程无副作用,逻辑清晰。
1. 词法分析:将表达式拆分为Token
首先需要把输入的表达式字符串(比如"3+4*(2-1)")拆分成有意义的单元(Token),包括数字、运算符、括号。这个过程用纯函数实现:
function tokenize(expression) { // 匹配数字(含小数)、运算符、括号的正则 const regex = /\d+\.?\d*|[-+*/()]/g; return expression.match(regex) || []; }
这个函数接收字符串,返回Token数组,比如输入"3.5+2*(5-1)"会得到["3.5", "+", "2", "*", "(", "5", "-", "1", ")"]。
2. 转换为后缀表达式(Shunting-yard算法)
中缀表达式(日常写的a+b*c)的运算符优先级和括号处理起来复杂,转成后缀表达式(ab*+)后可以按顺序直接计算。用纯函数实现转换逻辑,依赖运算符优先级映射:
// 运算符优先级,数值越高优先级越高 const operatorPrecedence = { '+': 1, '-': 1, '*': 2, '/': 2 }; function infixToPostfix(tokens) { const output = []; const operatorStack = []; for (const token of tokens) { // 数字直接加入输出队列 if (!isNaN(parseFloat(token))) { output.push(parseFloat(token)); } // 左括号压入栈 else if (token === '(') { operatorStack.push(token); } // 右括号:弹出栈内运算符直到遇到左括号 else if (token === ')') { let top = operatorStack.pop(); while (top !== '(') { output.push(top); top = operatorStack.pop(); } } // 运算符:按优先级处理栈内元素 else { while (operatorStack.length > 0 && operatorStack[operatorStack.length - 1] !== '(' && operatorPrecedence[operatorStack[operatorStack.length - 1]] >= operatorPrecedence[token]) { output.push(operatorStack.pop()); } operatorStack.push(token); } } // 弹出栈内剩余运算符 while (operatorStack.length > 0) { output.push(operatorStack.pop()); } return output; }
这个函数接收Token数组,返回后缀表达式数组,比如输入之前的Token数组会得到[3.5, 2, 5, 1, '-', '*', '+']。
3. 计算后缀表达式
最后遍历后缀表达式,用栈存储中间结果,遇到运算符就取出栈顶两个数计算,结果压回栈。这里用函数映射来处理运算,完全符合函数式风格:
// 运算符对应的运算函数 const operators = { '+': (a, b) => a + b, '-': (a, b) => a - b, '*': (a, b) => a * b, '/': (a, b) => { if (b === 0) throw new Error('除数不能为0'); return a / b; } }; function evaluatePostfix(postfix) { const stack = []; for (const token of postfix) { if (typeof token === 'number') { stack.push(token); } else { const b = stack.pop(); const a = stack.pop(); stack.push(operators[token](a, b)); } } return stack.pop(); }
整合完整计算器
把三个纯函数组合起来,得到最终的计算器函数:
function calculate(expression) { const tokens = tokenize(expression); const postfix = infixToPostfix(tokens); return evaluatePostfix(postfix); } // 测试示例 console.log(calculate("3+4*(2-1)")); // 7 console.log(calculate("3.5+2*(5-1)")); // 11.5 console.log(calculate("10/(5-3)")); // 5
函数式编程特点说明
- 所有核心函数都是纯函数:相同输入必定返回相同输出,不修改外部状态,只依赖参数传递。
- 用函数映射替代条件判断:
operators对象把运算符映射到对应的运算函数,符合函数式的"用函数抽象逻辑"思路。 - 无副作用:整个计算过程不会修改外部变量,所有状态都在函数内部的栈或数组中处理。
内容的提问来源于stack exchange,提问作者Mike Sphere
相关产品推荐
相关产品推荐

