在Javascript中计算公式,不带eval()

在Javascript中计算公式,不带eval(),javascript,eval,Javascript,Eval,我在一个网页(150+)中有一堆字段,需要在这些字段上运行方程才能生成结果 我当前存储的方程式如下所示: <input name="F7" type="text" class="numeric" data-formula="([C7]-[D7])/[E7]" readonly /> 当输入模糊时,我使用jQuery选择器使用数据公式属性迭代所有输入,获取公式,并使用正则表达式将指针(等式中的[C7])替换为相应的值 在那之后,我eval()得到一个结果的方程式,并将它放入正确的

我在一个网页(150+)中有一堆字段,需要在这些字段上运行方程才能生成结果

我当前存储的方程式如下所示:

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

当输入模糊时,我使用jQuery选择器使用
数据公式
属性迭代所有输入,获取公式,并使用正则表达式将指针(等式中的
[C7]
)替换为相应的值

在那之后,我
eval()
得到一个结果的方程式,并将它放入正确的输入中。这非常有效,但速度非常慢,会导致网页挂起几秒钟,如果每次输入模糊时都出现这种情况,那就很糟糕了

有没有一种方法可以不使用
eval()
而计算公式,例如“(1-2)/4”?这些方程也可能有函数,比如平方根(这使得
eval()
很好,因为我可以把
Math.sqrt()
放在公式中),数字可能是小数

注意:这个应用程序必须在IE7和IE8上运行,所以我不相信我可以使用Webworkers或类似的东西。我还考虑过只在点击“保存”按钮后运行这段代码,但如果可能的话,我更希望UI能够实时更新

有没有一种方法可以不使用
eval()
而计算公式,例如“(1-2)/4”

那么,您可以标记表达式并编写自己的求值器来模拟
eval
的功能。但是,尽管这在限制副作用方面可能是有用的(因为
eval
是一个非常大的打击),但它不太可能比
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
使用了
值中的值,而不是每次都重新计算所有值


如果
this.value
可能引用其他字段,则可以对其进行递归求值,但不必对所有输入进行求值。

验证:我会编写一个功能强大的正则表达式来验证输入,然后使用
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

var方程=['1+1','2+2','…']// 我会修改你的代码,只执行一次评估

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];

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

或者,您可以编写自己的方程式计算器。这相当困难,可能不会比eval更有效。你还得经历彭达斯订单的巨大麻烦


我建议您可以将这些方程合并成一个字符串,然后一次计算所有方程,然后一次检索所有结果。

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

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;
}
另一种方法是使用
新函数(…)

但我认为您不会找到与
eval
性能类似的产品:

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


这只是一些UAs上的一组有限的测试,但它应该能让您了解它在不同环境中的性能。

您可以使用
新函数来评估您的表达式

我知道这个答案已经晚了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()替换变量,以提供一个类似于代码段中示例用法的字符串。

是的,可以编写一个自滚解析器。为什么这比使用eval()要快呢?我不确定——会吗
eval()
的速度是出了名的慢。我的每一次通话都需要73毫秒,总共100次通话。使用自定义解析器可能会花费更少的时间。为什么在只有1个模糊的情况下计算所有输入?我总是可以只计算引用模糊单元格的单元格,但问题是级联效应——计算字段可能是页面中其他单元格的依赖项。我可能能够递归地找出什么依赖于发生变化的单元格——这肯定会更快……我已经完成了may页面中字段相互依赖的部分,最好的方法是找出依赖项的算法,并编写递归函数来级联它们。当然,除非有太多的依赖项,使计算量减少。我知道方程都可以——每次调用只需约70毫秒,x100,这是很长的时间。@AndrewM您可以将方程排队,并在最后计算所有表达式。结果可以保存在数组中。例如:
eval(“[”+等式列表连接(“,”)+“]”)结果为
eval(“[1+1,2+2]”)
导致数组
[2,4]
。这并不能回答问题。@t.J.Crowder我最初误解了这个问题。我现在编辑了答案。(解释为“如何使用
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"));