是否有一种无需使用eval
即可计算JavaScript中存储在字符串中的公式的方法?
通常我会做类似的事情
var apa = "12/5*9+9.4*2";
alert(eval(apa));
所以,有人知道eval
的替代方法吗?
这正是您应该使用eval的地方,否则您将不得不遍历字符串并生成数字。您将必须使用isNaN方法来执行此操作。
此解决方案还剪切空格并检查是否有重复的运算符
例如' 1+ 2 *2' // 5
,但' 1 + +2* 2 ' // Error
function calcMe(str) {
const noWsStr = str.replace(/\s/g, '');
const operators = noWsStr.replace(/[\d.,]/g, '').split('');
const operands = noWsStr.replace(/[+/%*-]/g, ' ')
.replace(/\,/g, '.')
.split(' ')
.map(parseFloat)
.filter(it => it);
if (operators.length >= operands.length){
throw new Error('Operators qty must be lesser than operands qty')
};
while (operators.includes('*')) {
let opIndex = operators.indexOf('*');
operands.splice(opIndex, 2, operands[opIndex] * operands[opIndex + 1]);
operators.splice(opIndex, 1);
};
while (operators.includes('/')) {
let opIndex = operators.indexOf('/');
operands.splice(opIndex, 2, operands[opIndex] / operands[opIndex + 1]);
operators.splice(opIndex, 1);
};
while (operators.includes('%')) {
let opIndex = operators.indexOf('%');
operands.splice(opIndex, 2, operands[opIndex] % operands[opIndex + 1]);
operators.splice(opIndex, 1);
};
let result = operands[0];
for (let i = 0; i < operators.length; i++) {
operators[i] === '+' ? (result += operands[i + 1]) : (result -= operands[i + 1])
}
return result
}
这表明比@vol7ron
的解决方案性能更高。检查此JSBenchmark
stringToMatch(str);
我使用stringToMatch(str)代替eval(),它可以正常计算简单的字符串值。
Mhh,您可以使用Function
-构造函数:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
function evil(fn) {
return new Function('return ' + fn)();
}
console.log( evil('12/5*9+9.4*2') ); // => 40.4
评估没有什么问题,尤其是在这种情况下。为了安全起见,您可以先使用正则表达式对字符串进行清理:
// strip anything other than digits, (), -+/* and .
var str = "12/5*9+9.4*2".replace(/[^-()\d/*+.]/g, '');
alert(eval(str));
评估是针对这样的条件构建的。
如果需要其他方法,则必须使用纯Javascript实现来实现eval的确切操作。
这是我想出的一个简单的基本示例(已更新(2011-06-26):带输入框的清洁器)。http://jsfiddle.net/vol7ron/6cdfA/
注:
编辑(2017-05-26)以使用SO摘录:
function calculate(input) {
var f = {
add: '+',
sub: '-',
div: '/',
mlt: '*',
mod: '%',
exp: '^'
};
// Create array for Order of Operation and precedence
f.ooo = [
[
[f.mlt],
[f.div],
[f.mod],
[f.exp]
],
[
[f.add],
[f.sub]
]
];
input = input.replace(/[^0-9%^*\/()\-+.]/g, ''); // clean up unnecessary characters
var output;
for (var i = 0, n = f.ooo.length; i < n; i++) {
// Regular Expression to look for operators between floating numbers or integers
var re = new RegExp('(\\d+\\.?\\d*)([\\' + f.ooo[i].join('\\') + '])(\\d+\\.?\\d*)');
re.lastIndex = 0; // take precautions and reset re starting pos
// Loop while there is still calculation for level of precedence
while (re.test(input)) {
output = _calculate(RegExp.$1, RegExp.$2, RegExp.$3);
if (isNaN(output) || !isFinite(output))
return output; // exit early if not a number
input = input.replace(re, output);
}
}
return output;
function _calculate(a, op, b) {
a = a * 1;
b = b * 1;
switch (op) {
case f.add:
return a + b;
break;
case f.sub:
return a - b;
break;
case f.div:
return a / b;
break;
case f.mlt:
return a * b;
break;
case f.mod:
return a % b;
break;
case f.exp:
return Math.pow(a, b);
break;
default:
null;
}
}
}
label {
display: inline-block;
width: 4em;
}
<div>
<label for="input">Equation: </label>
<input type="text" id="input" value="12/5*9+9.4*2-1" />
<input type="button"
value="calculate"
onclick="getElementById('result').value = calculate(getElementById('input').value)" />
</div>
<div>
<label for="result">Result: </label>
<input type="text" id="result" />
</div>
[这里是Shunting-yard algorithm的实现,并额外支持一元前缀(例如-
)和后缀(例如!
)运算符,以及函数(例如sqrt()
)表示法。使用Calculation.defineOperator
方法可以轻松定义更多运算符/功能:
"use strict";
class Calculation {
constructor() {
this._symbols = {};
this.defineOperator("!", this.factorial, "postfix", 6);
this.defineOperator("^", Math.pow, "infix", 5, true);
this.defineOperator("*", this.multiplication, "infix", 4);
this.defineOperator("/", this.division, "infix", 4);
this.defineOperator("+", this.last, "prefix", 3);
this.defineOperator("-", this.negation, "prefix", 3);
this.defineOperator("+", this.addition, "infix", 2);
this.defineOperator("-", this.subtraction, "infix", 2);
this.defineOperator(",", Array.of, "infix", 1);
this.defineOperator("(", this.last, "prefix");
this.defineOperator(")", null, "postfix");
this.defineOperator("min", Math.min);
this.defineOperator("sqrt", Math.sqrt);
}
// Method allowing to extend an instance with more operators and functions:
defineOperator(symbol, f, notation = "func", precedence = 0, rightToLeft = false) {
// Store operators keyed by their symbol/name. Some symbols may represent
// different usages: e.g. "-" can be unary or binary, so they are also
// keyed by their notation (prefix, infix, postfix, func):
if (notation === "func") precedence = 0;
this._symbols[symbol] = Object.assign({}, this._symbols[symbol], {
[notation]: {
symbol, f, notation, precedence, rightToLeft,
argCount: 1 + (notation === "infix")
},
symbol,
regSymbol: symbol.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
+ (/\w$/.test(symbol) ? "\\b" : "") // add a break if it's a name
});
}
last(...a) { return a[a.length-1] }
negation(a) { return -a }
addition(a, b) { return a + b }
subtraction(a, b) { return a - b }
multiplication(a, b) { return a * b }
division(a, b) { return a / b }
factorial(a) {
if (a%1 || !(+a>=0)) return NaN
if (a > 170) return Infinity;
let b = 1;
while (a > 1) b *= a--;
return b;
}
calculate(expression) {
let match;
const values = [],
operators = [this._symbols["("].prefix],
exec = _ => {
let op = operators.pop();
values.push(op.f(...[].concat(...values.splice(-op.argCount))));
return op.precedence;
},
error = msg => {
let notation = match ? match.index : expression.length;
return `${msg} at ${notation}:\n${expression}\n${' '.repeat(notation)}^`;
},
pattern = new RegExp(
// Pattern for numbers
"\\d+(?:\\.\\d+)?|"
// ...and patterns for individual operators/function names
+ Object.values(this._symbols)
// longer symbols should be listed first
.sort( (a, b) => b.symbol.length - a.symbol.length )
.map( val => val.regSymbol ).join('|')
+ "|(\\S)", "g"
);
let afterValue = false;
pattern.lastIndex = 0; // Reset regular expression object
do {
match = pattern.exec(expression);
const [token, bad] = match || [")", undefined],
notNumber = this._symbols[token],
notNewValue = notNumber && !notNumber.prefix && !notNumber.func,
notAfterValue = !notNumber || !notNumber.postfix && !notNumber.infix;
// Check for syntax errors:
if (bad || (afterValue ? notAfterValue : notNewValue)) return error("Syntax error");
if (afterValue) {
// We either have an infix or postfix operator (they should be mutually exclusive)
const curr = notNumber.postfix || notNumber.infix;
do {
const prev = operators[operators.length-1];
if (((curr.precedence - prev.precedence) || prev.rightToLeft) > 0) break;
// Apply previous operator, since it has precedence over current one
} while (exec()); // Exit loop after executing an opening parenthesis or function
afterValue = curr.notation === "postfix";
if (curr.symbol !== ")") {
operators.push(curr);
// Postfix always has precedence over any operator that follows after it
if (afterValue) exec();
}
} else if (notNumber) { // prefix operator or function
operators.push(notNumber.prefix || notNumber.func);
if (notNumber.func) { // Require an opening parenthesis
match = pattern.exec(expression);
if (!match || match[0] !== "(") return error("Function needs parentheses")
}
} else { // number
values.push(+token);
afterValue = true;
}
} while (match && operators.length);
return operators.length ? error("Missing closing parenthesis")
: match ? error("Too many closing parentheses")
: values.pop() // All done!
}
}
Calculation = new Calculation(); // Create a singleton
// I/O handling
function perform() {
const expr = document.getElementById('expr').value,
result = Calculation.calculate(expr);
document.getElementById('out').textContent = isNaN(result) ? result : '=' + result;
}
document.getElementById('expr').addEventListener('input', perform);
perform();
// Tests
const tests = [
{ expr: '1+2', expected: 3 },
{ expr: '1+2*3', expected: 7 },
{ expr: '1+2*3^2', expected: 19 },
{ expr: '1+2*2^3^2', expected: 1025 },
{ expr: '-3!', expected: -6 },
{ expr: '12---11+1-3', expected: -1 },
{ expr: 'min(2,1,3)', expected: 1 },
{ expr: '(2,1,3)', expected: 3 },
{ expr: '4-min(sqrt(2+2*7),9,5)', expected: 0 },
{ expr: '2,3,10', expected: 10 }
]
for (let {expr, expected} of tests) {
let result = Calculation.calculate(expr);
console.assert(result === expected, `${expr} should be ${expected}, but gives ${result}`);
}
#expr { width: 100%; font-family: monospace }
Expression: <input id="expr" value="min(-1,0)+((sqrt(16)+(-4+7)!*---4)/2)^2^3"><p>
<pre id="out"></pre>
如果您不想使用eval,则必须使用现有的表达式求值器库。
http://silentmatt.com/javascript-expression-evaluator/
http://www.codeproject.com/KB/scripting/jsexpressioneval.aspx
您也可以自己滚动一个:)
[您不能,最多只能做一些反击,例如解析数字,然后用开关将其分开,然后进行操作。除此之外,在这种情况下,我将使用eval。
那会是((实际的实现会稍微复杂一些,特别是如果您考虑使用括号,但是您知道了的话)]
function operate(text) {
var values = text.split("+");
return parseInt(values[0]) + parseInt(values[1]);
}
alert(operate("9+2"));
仍然,我认为最好的选择是使用eval,因为您能够信任字符串的来源。
我花了几个小时不使用eval()
来实现所有算术规则,最后我在npm string-math上发布了一个软件包。一切都在描述中。享受
如果您正在寻找与eval
等效的语法,则可以使用new Function
。范围界定方面略有不同,但它们的行为大多相同,包括面临许多相同的安全风险:
let str = "12/5*9+9.4*2"
let res1 = eval(str)
console.log('res1:', res1)
let res2 = (new Function('return '+str)())
console.log('res2:', res2)