在没有eval()的情况下评估Javascript中的公式

时间:2012-07-10 21:45:09

标签: javascript eval

我在网页(150+)中有很多字段需要运行方程式才能生成结果。

我目前存储这样的等式:

<input name="F7" type="text" class="numeric" data-formula="([C7]-[D7])/[E7]" readonly />

当输入模糊时,我使用jQuery选择器迭代所有带有data-formula属性的输入,取公式,并使用正则表达式替换指针(等式中的[C7])用适当的价值观。

之后,我eval()等式得到一个结果,并把它放在正确的输入中。这很好用,但非常慢,导致网页挂起几秒钟,如果每次输入模糊时都会发生这种情况很糟糕。

有没有办法在不使用eval()的情况下评估等式,例如“(1-2)/ 4”?这些方程也可能有函数,例如平方根(这使得eval()很好,因为我可以将Math.sqrt()放在公式中),并且数字可能是小数。

注意:此应用程序必须在IE7和8上运行,所以我不相信我可以使用Webworkers或类似的东西。我还考虑在点击“保存”按钮后仅运行此代码,但我希望UI尽可能更新直播

7 个答案:

答案 0 :(得分:4)

  

有没有办法在不使用eval()的情况下评估等式,例如“(1-2)/ 4”?

好吧,您可以对表达式进行标记,并编写自己的评估程序,模仿eval的作用。虽然这在限制副作用方面可能有用(因为eval是一个非常大的锤子),但是 不太可能比{更好{1}}确实。

但是,您可以执行的操作是缓存评估所有其他输入的结果,以便您只评估实际模糊的输入。那应该是非常有效的。

例如,假设您有此全局对象:

eval

...然后将此var values = { A7: /* initial value for A7 */, B7: /* initial value for B7 */, C7: /* initial value for C7 */, D7: /* initial value for D7 */, E7: /* initial value for E7 */, F7: /* initial value for F7 */, /* etc */ }; 处理程序附加到所有输入:

blur

...其中$("input").blur(function() { values[this.id] = this.value; // Or parseInt(this.value, 10), or parseFloat(this.value), etc. doTheEvaluation(); }); 使用doTheEvaluation中的值,而不是每次重新计算所有值。

如果values可能引用其他字段,您可以对其进行递归评估 - 但不评估输入的所有

答案 1 :(得分:4)

我只知道两种选择,一种是使用动态写入页面的script元素,例如:

function evaluate(formula)
{
  var script = document.createElement("script");
  script.type = "text/javascript";
  script.text = "window.__lr = " + formula + ";";
  document.body.appendChild(script);
  document.body.removeChild(script);

  var r = window.__lr;

  return r;
}

另一种方法是使用new Function(...)

function evaluate3(formula)
{
  var func = new Function("return " + formula);
  return func();
}

但我认为你找不到与eval产生类似效果的东西:http://jsperf.com/alternative-evaluation

eval的性能因浏览器和平台而异,您是否考虑过特定​​的浏览器/平台组合?改进的浏览器中较新的JavaScript引擎将提供优化的eval

Test Results

这只是对一些UA的一组有限测试,但它应该让您了解它在不同环境中的表现。

答案 2 :(得分:1)

验证:我会编写一个强大的正则表达式来验证输入,然后使用eval来评估它是否安全。

评估:关于评估的速度:如果这是一个大问题,你可以排队所有方程式(将它存储在一个数组中),并立即对它们进行评估:

var equations = ['1+1', '2+2', '...'];   //<-- Input from your fields
var toBeEvald = '[' + equations.join(',') + '];';
var results = eval(toBeEvald);
// result[0] = 2
// result[1] = 4, etc

答案 3 :(得分:1)

我会修改你的代码只执行一个eval。

var expressions = []
// for each field
// expressions.push("id:" + parsedExpression);
var members = expressions.join(",");
var resultObj = eval("({" + members + "})");
// for each field 
document.getElementById(id).value = resultObj[id];

答案 4 :(得分:0)

如果您有可靠的互联网连接,您可以连接到谷歌并使用他们的服务来评估表达式。谷歌有一个非常强大的服务器,你所要做的就是发送一个请求,队列就是等式并检索它。当然,这可能会更慢或更快,具体取决于互联网速度/浏览器速度。

或者,您可以编写自己的等式评估程序。这非常困难,并且可能不会比eval更有效。您还必须经历PEMDAS订单的巨大麻烦。

我建议您可以将这些方程式合并为一个字符串,并同时评估所有方法,并一次性检索所有结果。

答案 5 :(得分:0)

您可以使用new Function来评估您的表达式

答案 6 :(得分:0)

我确实知道这个答案已经晚了8年,但是我想我会添加自己的贡献,因为这个问题出现在我正在研究的项目中。就我而言,我使用的是Nodejs,但该解决方案也应适用于浏览器。

let parens = /\(([0-9+\-*/\^ .]+)\)/             // Regex for identifying parenthetical expressions
let exp = /(\d+(?:\.\d+)?) ?\^ ?(\d+(?:\.\d+)?)/ // Regex for identifying exponentials (x ^ y)
let mul = /(\d+(?:\.\d+)?) ?\* ?(\d+(?:\.\d+)?)/ // Regex for identifying multiplication (x * y)
let div = /(\d+(?:\.\d+)?) ?\/ ?(\d+(?:\.\d+)?)/ // Regex for identifying division (x / y)
let add = /(\d+(?:\.\d+)?) ?\+ ?(\d+(?:\.\d+)?)/ // Regex for identifying addition (x + y)
let sub = /(\d+(?:\.\d+)?) ?- ?(\d+(?:\.\d+)?)/  // Regex for identifying subtraction (x - y)

/**
 * Evaluates a numerical expression as a string and returns a Number
 * Follows standard PEMDAS operation ordering
 * @param {String} expr Numerical expression input
 * @returns {Number} Result of expression
 */
function evaluate(expr)
{
    if(isNaN(Number(expr)))
    {
        if(parens.test(expr))
        {
            let newExpr = expr.replace(parens, function(match, subExpr) {
                return evaluate(subExpr);
            });
            return evaluate(newExpr);
        }
        else if(exp.test(expr))
        {
            let newExpr = expr.replace(exp, function(match, base, pow) {
                return Math.pow(Number(base), Number(pow));
            });
            return evaluate(newExpr);
        }
        else if(mul.test(expr))
        {
            let newExpr = expr.replace(mul, function(match, a, b) {
                return Number(a) * Number(b);
            });
            return evaluate(newExpr);
        }
        else if(div.test(expr))
        {
            let newExpr = expr.replace(div, function(match, a, b) {
                if(b != 0)
                    return Number(a) / Number(b);
                else
                    throw new Error('Division by zero');
            });
            return evaluate(newExpr);
        }
        else if(add.test(expr))
        {
            let newExpr = expr.replace(add, function(match, a, b) {
                return Number(a) + Number(b);
            });
            return evaluate(newExpr);
        }
        else if(sub.test(expr))
        {
            let newExpr = expr.replace(sub, function(match, a, b) {
                return Number(a) - Number(b);
            });
            return evaluate(newExpr);
        }
        else
        {
            return expr;
        }
    }
    return Number(expr);
}
// Example usage
//console.log(evaluate("2 + 4*(30/5) - 34 + 45/2"));

在原始文章中,可以使用String.replace()替换变量,以提供类似于代码段中示例用法的字符串。