JavaScript 中的动态公式求值:eval() 或非 eval()

Dynamic formula evaluation in JavaScript: eval() or not eval()

我正在开发一个网络应用程序,管理员可以在其中定义和调整需要根据最终用户提供的输入值(数字)进行评估的公式。

为了清楚起见,这里是我要排除的内容的简化示例:

const obj = {
    type: "External wall in contact with the ground",
    layer: {
        base: {
            type: "Reinforced concrete (reinforcement 5 vol.%)",
            thickness: 100,                         // <-- user value
            lambda: 2.3,                            // <-- user value
            _r: "{{thickness}}/{{lambda}}/1000",    // <-- admin defined
            r: 0
        },
        waterproofing: {
            type: "Bitumen sheets (single layer)",
            share: 1,           // <-- user value
            _r: "{{share}}",    // <-- admin defined
            r: 0,
        },
        insulation: {
            type: "XPS",
            thickness: 100,                                 // <-- user value
            share: 1,                                       // <-- user value
            lambda: 0.040,                                  // <-- user value
            _r: "{{thickness}}*{{share}}/{{lambda}}/1000",  // <-- admin defined
            r: 0
        }
    }
}

Object.entries(obj.layer).forEach(([key, object]) => {
    var formula = object._r
    Object.keys(object).forEach(k =>
        formula = formula.replace(`{{${k}}}`, object[k])
    )

    obj.layer[key].r = eval(formula)
})

console.log(obj)

_r是管理员定义的公式。 {{value}} 是最终用户提供的值。

循环通过 obj.layer 属性计算公式并将答案保存在 r 中。

结果将是这个对象:

{
  type: 'External wall in contact with the ground',
  layer: {
    base: {
      type: 'Reinforced concrete (reinforcement 5 vol.%)',
      thickness: 100,
      lambda: 2.3,
      _r: '{{thickness}}/{{lambda}}/1000',
      r: 0.043478260869565216
    },
    waterproofing: {
      type: 'Bitumen sheets (single layer)',
      share: 1,
      _r: '{{share}}',
      r: 1
    },
    insulation: {
      type: 'XPS',
      thickness: 100,
      share: 1,
      lambda: 0.04,
      _r: '{{thickness}}*{{share}}/{{lambda}}/1000',
      r: 2.5
    }
  }
}

让我们跳过我没有验证对象结构并确保所有值都可用的事实。

我知道 eval() 被认为是“危险的”。一个不太好的选择是 Function()。然而,并不完美。

到目前为止,我看到了 3 种可能性:

  1. 只有管理员可以更改公式。因此,执行恶意代码的风险非常低。我需要的是 validate/sanitize 值(类似于 isFloat()),仅此而已。

  2. 使用 mathjs 库,它提供了一个不错的 evaluate() 功能:

const node2 = math.parse('x^a')
const code2 = node2.compile()
let scope = {
    x: 3,
    a: 2
}
code2.evaluate(scope) // 9
  1. 使用像 http://zaa.ch/jison/ 这样的解析器生成器,但对于我想做的事情来说似乎有些过分了..

老实说,我觉得在我的具体情况下使用 eval() 是合理的:具有动态值的动态公式。我可以使用像 mathjs 这样的外部库,但我觉得我不需要它来进行如此简单的操作。

我真的很想知道你对这个问题的想法,如果有的话,我想听听你的建议!

PS: 是的,这个问题已经被问过了。虽然,我发现的最相似的问题是几年前提出(和回答)的。我想就此事提出新的意见。


基于@Sleavely 的回答,并进一步阅读该主题,mathjs 似乎是最合理的解决方案;-)

基于@Sleavely 的回答(见下面引用的评论),并进一步阅读该主题,mathjs 似乎是最合理的解决方案:

Third-party admins may be the only ones who can edit the code, but where will it actually run? In your backend? On visitors' computers? I strongly recommend you avoid eval(), lest you be blamed for whatever Bitcoin miner visitors end up contracting from your application. With that in mind, I think that you're on the right track with your 2nd option. Evaluating the formula against a set of predefined variables strikes me as safe enough. Looks like mathjs actively avoids eval(): https://github.com/josdejong/mathjs/blob/master/docs/expressions/security.md