在javascript中计算字符串值,而不是使用eval
Calculate string value in javascript, not using eval
有没有办法在不使用eval()
的情况下计算存储在 JavaScript 字符串中的公式?
通常我会做这样的事情
var apa = "12/5*9+9.4*2";
console.log(eval(apa));
那么,有谁知道eval()
的替代品吗?
嗯,你可以使用 Function()
构造函数:
function evil(fn) {
return new Function('return ' + fn)();
}
console.log(evil('12/5*9+9.4*2')); // => 40.4
eval 没有错,尤其是对于这样的情况。 为了安全起见,您可以先使用正则表达式清理字符串:
// strip anything other than digits, (), -+/* and .
var str = "12/5*9+9.4*2".replace(/[^-()'d/*+.]/g, '');
console.log(eval(str));
Eval 就是为这样的条件而构建的。
如果你想要另一种方法,你必须使用一个纯粹的Javascript实现,这正是eval将要做的事情。
- 困难的部分不是数字和运算符的解析
- 困难的部分是应用操作顺序和递归控制
这是我想出的一个快速基本示例(更新(2011-06-26(:带输入框的清洁器(。
http://jsfiddle.net/vol7ron/6cdfA/
注意:
- 它只处理基本运算符
- 它不检查数字的有效性(例如:除以零(
- 它尚未实现括号操作
- 由于所有这些原因以及更多原因,Eval将是一个更好的选择
编辑 (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>
您应该使用eval()
的地方,否则您将不得不遍历字符串并生成数字。您必须使用 Number.isNaN()
方法来执行此操作。
这是分流场算法的实现,它额外支持一元前缀(例如 -
(和后缀(例如 !
( 运算符和函数(例如 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);
this.defineOperator("pi", Math.PI); // A constant
}
// 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);
let [token, bad] = match || [")", undefined];
// Replace constant names (like PI) with corresponding value
if (typeof this._symbols[token]?.func?.f === "number") token = this._symbols[token].func?.f;
const 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 },
{ expr: 'pi*2', expected: Math.PI*2 },
]
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()
最后我发布了一个关于npm字符串数学的包。一切都在描述中。享受
此解决方案还剪辑空格并检查重复运算符
例如 ' 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
如果你正在寻找一个语法等同于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)
你不能,最多你可以做一些反驳的事情,比如解析数字,然后用开关分隔操作,然后进行它们。除此之外,在这种情况下我会使用 eval。
那将是这样的(真正的实现会稍微复杂一些,特别是如果你考虑使用括号,但你明白了(
function operate(text) {
var values = text.split("+");
return parseInt(values[0]) + parseInt(values[1]);
}
console.log(operate("9+2"));
不过,我认为你可以做出的最佳选择是使用 eval,因为你能够信任字符串的来源。
GitHub上还有一个开源实现,evaluator.js和一个NPM包。
来自自述文件:计算器.js 是一个用于评估数学表达式的小型零依赖性模块。
支持所有主要操作、常量和方法。此外,Evaluator.js 智能地报告无效语法,例如误用的运算符、缺少操作数或不匹配的括号。
计算器.js由同名的桌面计算器应用程序使用。在网站上观看现场演示。
注意:此解决方案中没有使用纯硬编码的库
我的解决方案也考虑了括号,如8+6(7(-1))
或8+6(7(-1))
您可以^, *, /, +, -
执行以下操作
要计算字符串,请使用calculate(tokenize(pieval("8+6(7(-1))").join("")))
function tokenize(s) {
// --- Parse a calculation string into an array of numbers and operators
const r = [];
let token = '';
for (const character of s) {
if ('^*/+-'.indexOf(character) > -1) {
if (token === '' && character === '-') {
token = '-';
} else {
r.push(parseFloat(token), character);
token = '';
}
} else {
token += character;
}
}
if (token !== '') {
r.push(parseFloat(token));
}
return r;
}
function calculate(tokens) {
// --- Perform a calculation expressed as an array of operators and numbers
const operatorPrecedence = [{'^': (a, b) => Math.pow(a, b)},
{'*': (a, b) => a * b, '/': (a, b) => a / b},
{'+': (a, b) => a + b, '-': (a, b) => a - b}];
let operator;
for (const operators of operatorPrecedence) {
const newTokens = [];
for (const token of tokens) {
if (token in operators) {
operator = operators[token];
} else if (operator) {
newTokens[newTokens.length - 1] =
operator(newTokens[newTokens.length - 1], token);
operator = null;
} else {
newTokens.push(token);
}
}
tokens = newTokens;
}
if (tokens.length > 1) {
console.log('Error: unable to resolve calculation');
return tokens;
} else {
return tokens[0];
}
}
function pieval(input) {
let openParenCount = 0;
let myOpenParenIndex = 0;
let myEndParenIndex = 0;
const result = [];
for (let i = 0; i < input.length; i++) {
if (input[i] === "(") {
if (openParenCount === 0) {
myOpenParenIndex = i;
// checking if anything exists before this set of parentheses
if (i !== myEndParenIndex) {
if(!isNaN(input[i-1])){
result.push(input.substring(myEndParenIndex, i) + "*");
}else{
result.push(input.substring(myEndParenIndex, i));
}
}
}
openParenCount++;
}
if (input[i] === ")") {
openParenCount--;
if (openParenCount === 0) {
myEndParenIndex = i + 1;
// recurse the contents of the parentheses to search for nested ones
result.push(pieval(input.substring(myOpenParenIndex + 1, i)));
}
}
}
// capture anything after the last parentheses
if (input.length > myEndParenIndex) {
result.push(input.substring(myEndParenIndex, input.length));
}
//console.log(cal(result))
let response = cal(result);
return result;
}
function cal(arr) {
let calstr = "";
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] != "string") {
if (cal(arr[i]) < 0) {
arr[i] = `${cal(arr[i])}`;
} else {
arr[i] = `${cal(arr[i])}`;
}
}
if (typeof arr[i] === "string") {
calstr += arr[i];
}
if (i == arr.length - 1) {
//console.log("cal" ,calstr,calculate(tokenize(calstr)) );
return calculate(tokenize(calstr));
}
}
}
console.log(calculate(tokenize(pieval("8+6(7(-1))").join("")))); // ["1+",["2-",["3+4"]]]
console.log(calculate(tokenize(pieval("1+(1+(2(4/4))+4)").join("")))); // ["1+",["2-",["3+4"]]]
- 推荐在JavaScript中执行存储为字符串的函数,而不是使用eval
- Javascript eval()无法处理传递的字符串
- Semi-sandboxing Javascript eval
- JavaScript 可以在这里使用 eval 吗?
- 如何使用uglifyjs/uglifyjs2解析walk和eval javascript表达式
- 当eval只执行服务器端数据时,在javascript中使用eval是否安全
- javascript eval方法语法
- 在Javascript中使用eval的替代方案
- 如何使eval'd调试器可访问的javascript代码
- JavaScript eval() and const
- 想要将 JSON 字符串传递给 javascript 变量 [ JSON.parse(),eval()] 对我不起作用,
- “语法错误:意外的令牌)”在 javascript 调用中,当 eval(value) 为空时
- mongo javascript --eval is failing
- 如何在javascript中的eval函数中添加单引号
- Javascript eval查询字符串
- 使用 eval JavaScript 创建动态变量
- Haskell, MongoDB, eval JavaScript
- eval() javascript如何安全地获得指示,如果它可以评估和防止异常
- 关联数组不能与eval() JavaScript一起工作
- iMacros w EVAL([javascript]} FORMAT尝试时出现错误,没有解决方案