Javascript 在javascript中计算字符串值,不使用eval

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6479236/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 21:56:48  来源:igfitidea点击:

Calculate string value in javascript, not using eval

javascripteval

提问by Eric Herlitz

Is there a way to calculate a formula stored in a string in JavaScript without using eval?

有没有办法计算存储在 JavaScript 中的字符串中的公式而不使用eval

Normally I would do something like

通常我会做类似的事情

var apa = "12/5*9+9.4*2";
alert(eval(apa));

So, does anyone know about alternatives to eval?

那么,有没有人知道替代品eval

采纳答案by Troy SK

This exactly the place where you should be using eval, or you will have to loop through the string and generate the numbers. You will have to use isNaN method to do it.

这正是您应该使用 eval 的地方,否则您将不得不遍历字符串并生成数字。您将不得不使用 isNaN 方法来做到这一点。

回答by yckart

Mhh, you could use the Function-constructor:

嗯,你可以使用 - 构造函数Function

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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

回答by Mark Kahn

There's nothing wrong with eval, especially for cases like this. You can sanitize the string with a regex first to be safe:

eval 没有任何问题,尤其是对于这样的情况。您可以先使用正则表达式清理字符串以确保安全:

// strip anything other than digits, (), -+/* and .
var str = "12/5*9+9.4*2".replace(/[^-()\d/*+.]/g, '');
alert(eval(str));

回答by vol7ron

Eval was built for conditions like this.

Eval 就是为这样的条件而构建的。

If you wanted another method, you'd have to use a pure Javascript implementation of the exact thing eval is going to do.

如果您想要另一种方法,则必须使用 eval 将要执行的确切操作的纯 Javascript 实现。

  • The hard part is notthe parsing of numbers and operators
  • The hard part isapplying order of operation and recursive control
  • 困难的部分不是数字和运算符的解析
  • 困难的部分应用操作顺序和递归控制

Here's a quick basic example I came up with (updated(2011-06-26): cleaner w/ input boxes).
http://jsfiddle.net/vol7ron/6cdfA/

这是我想出的一个快速基本示例(更新(2011-06-26):带有输入框的清洁器)。
http://jsfiddle.net/vol7ron/6cdfA/

Note:

笔记:

  • it only handles the basic operators
  • it does not check the validity of the numbers (example: divide by zero)
  • it has not implemented parenthetical operation
  • for all these reasons and more, eval would be a better choice
  • 它只处理基本的操作符
  • 它不检查数字的有效性(例如:除以零)
  • 它没有实现括号操作
  • 出于所有这些以及更多原因, eval 将是更好的选择

Edit (2017-05-26) to use SO Snippet:

编辑 (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., RegExp., RegExp.);
      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>

回答by trincot

Here is an implementation of the Shunting-yard algorithmwith additional support for unary prefix (e.g. -) and postfix (e.g. !) operators, and function (e.g. sqrt()) notations. More operators/functions can be easily defined with the Calculation.defineOperatormethod:

这里是Shunting-yard 算法的一个实现,额外支持一元前缀(例如-)和后缀(例如!)运算符,以及函数(例如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>

回答by parapura rajkumar

If you don't want to use eval you will have to use an existing expression evaluator library.

如果您不想使用 eval,则必须使用现有的表达式评估器库。

http://silentmatt.com/javascript-expression-evaluator/

http://silentmatt.com/javascript-expression-evaluator/

http://www.codeproject.com/KB/scripting/jsexpressioneval.aspx

http://www.codeproject.com/KB/scripting/jsexpressioneval.aspx

You can also roll one of your own :)

你也可以推出你自己的一个:)

回答by vol7ron

If you're looking for a syntactical equivalent to eval, you could use new Function. There are slight differences regarding scoping, but they mostly behave the same, including exposure to much of the same security risks:

如果您正在寻找相当于 的语法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)

回答by nikkypizza

This solution also clips whitespaces and checks for duplicating operators

此解决方案还剪辑空格并检查重复运算符

e.g. ' 1+ 2 *2' // 5but ' 1 + +2* 2 ' // Error

例如' 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
}

This shows to be more performant than @vol7ron's solution. Check this JSBenchmark

这表明比@vol7ron的解决方案性能更高。检查这个JSBenchmark

回答by Pawe?

I spent a couple of hours to implement all the arithmetical rules without using eval()and finally I published a package on npm string-math. Everything is in the description. Enjoy

我花了几个小时在不使用的情况下实现了所有的算术规则eval(),最后我在 npm string-math上发布了一个包。一切都在描述中。享受

回答by bevacqua

You can't, at most you could do something retort like parsing the numbers and then separating the operations with a switch, and making them. Other than that, I'd use eval in this case.

你不能,最多你可以做一些反驳的事情,比如解析数字,然后用开关分离操作,然后制作它们。除此之外,我会在这种情况下使用 eval 。

That would be something like (a real implementation will be somewhat more complex, specially if you consider the use of parenthesis, but you get the idea)

这将类似于(真正的实现会更复杂一些,特别是如果您考虑使用括号,但您明白了)

    function operate(text) {
        var values = text.split("+");

        return parseInt(values[0]) + parseInt(values[1]);
    }

    alert(operate("9+2"));

Still I think the best choice you can make is to use eval, given that you're able to trust the source of the string.

我仍然认为您可以做出的最佳选择是使用 eval,因为您可以信任字符串的来源。