V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
yangzh
V2EX  ›  JavaScript

如何不用`eval`实现前端的自由输入的计算器?

  •  
  •   yangzh · 2013-03-01 20:02:39 +08:00 · 6191 次点击
    这是一个创建于 4318 天前的主题,其中的信息可能已经有所发展或是发生改变。
    要实现的效果:在页面上有一个`<input>` 的输入框,用户在其输入一串数学字符串,js 接收,例如 `var str = '1+2*3-exp(4)^sin(pi)`,用函数 `calc(str)` 来计算表达式。其中表达式允许的有加减乘除乘方开方和 https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math 里面的符号。

    我想不通的是,这个 `calc(str='')` 的 js 的函数如何比较简单地实现?想了一下,似乎只能够检测字符串,把 `'sin'` 之类的变成 `'Math.sin'`,再用 `eval`对整个(字符串)表达式进行运算。但是众所周知的是, `eval` 应该要避免,但我怎样也想不出怎样(简单地)避开这个函数。
    16 条回复    1970-01-01 08:00:00 +08:00
    dreampuf
        1
    dreampuf  
       2013-03-01 20:03:29 +08:00
    "众所周知" 被代表了。
    miaoever
        2
    miaoever  
       2013-03-01 20:10:06 +08:00
    yangzh
        3
    yangzh  
    OP
       2013-03-01 20:58:36 +08:00
    @dreampuf 我错
    pppab
        4
    pppab  
       2013-03-01 20:59:38 +08:00
    先词法、语法分析,在分析过程中进行计算。
    yangzh
        5
    yangzh  
    OP
       2013-03-01 21:02:08 +08:00
    @miaoever 是的。。理论上可以用“翻译”成前缀表达式。但是这么做真心很大费周章吧
    acecode
        6
    acecode  
       2013-03-02 00:05:54 +08:00
    避免使用eval的原因主要是
    acecode
        7
    acecode  
       2013-03-02 00:14:29 +08:00   ❤️ 1
    (擦,本来想换行的。。。忽略上一条吧)避免使用eval的原因主要是 1.大段参数的效率比较慢, 2.变量作用域默认(好像)是全局作用域, 3.动态内容可能引入精心构造的恶意代码; 如果只是做计算器用途的话, 可以考虑用字符串替换+正则过滤来处理, 比如: 先过滤下常用函数表(sin, cos, ...), 然后再用正则匹配下/[0-9+-*/()]+/,如果执行出错就用try catch捕获下
    qiao
        8
    qiao  
       2013-03-02 08:36:35 +08:00   ❤️ 2
    樓主應該先去學下編譯原理。 然後可以使用 js 的 parser generator 如 jison http://zaach.github.com/jison/demos/calc/ 或者 pegjs http://pegjs.majda.cz/online 來實現你想要的功能。(個人比較喜歡 pegjs )
    jabbany
        9
    jabbany  
       2013-03-02 09:14:43 +08:00   ❤️ 1
    可以参考一下:https://github.com/silentmatt/js-expression-eval
    安全的代替EVAL,可以自己定义运算符号和函数
    yangzh
        10
    yangzh  
    OP
       2013-03-02 11:17:40 +08:00
    @acecode 我想到最方便的方法就是这样子了

    @jabbany 这个似乎符合我想实现的方法

    @qiao 我总觉得这个问题应该很简单,不用这么复杂。另外我没有系统学过编译原理,惨。
    Mutoo
        11
    Mutoo  
       2013-03-02 13:25:11 +08:00   ❤️ 1
    1)使用现成的 js 库,可以在网上找到很多,如

    http://www.codeproject.com/Articles/12116/JavaScript-Mathematical-Expression-Evaluator

    2)学习编译原理(初级),自己实现一个。

    另外给你一个计算器工作原理的介绍:
    http://zh.wikipedia.org/wiki/逆波兰表示法
    Cadina
        12
    Cadina  
       2013-03-02 14:01:15 +08:00
    中缀表达式变成前缀表达式就行了,比如:
    (3+5)*(2-4)
    (* (+ 3 5) (- 2 4))
    然后按照表达式树递归计算
    yangzh
        13
    yangzh  
    OP
       2013-03-02 16:33:28 +08:00
    @Mutoo
    @Cadina 前缀表达,逆波兰表示,我学过,有想过这个最笨的方法,相当于写一个编译器了。

    现在看来我搜索能力不行,之前搜索了很久都没有“现成 js 库”。现在有楼上几位的网址就知道其实是真的有的。
    middleware
        14
    middleware  
       2013-03-02 17:50:04 +08:00
    你不用寫一個完全的編譯器,只是一個簡單的 parser,大約四五條 BNF 語法,生成 AST 之後再從葉子節點開始求值。
    dingstyle
        15
    dingstyle  
       2013-03-03 10:35:32 +08:00
    楼上几位说用编译原理转AST都把问题复杂化了吧,这类表达式用调度场算法[1]转换成逆波兰表达式[2]再计算就可以了。

    [1]: http://en.wikipedia.org/wiki/Shunting-yard_algorithm
    [2]: http://en.wikipedia.org/wiki/Reverse_Polish_notation
    FrankFang128
        16
    FrankFang128  
       2013-03-03 12:33:01 +08:00
    1.大段参数的效率比较慢。
    效率这种事还是要测试才知道。如果慢得可以接受的话,就没问题。
    2.变量作用域默认(好像)是全局作用域。
    你在eval加个闭包不就没事了。
    3.动态内容可能引入精心构造的恶意代码; 如果只是做计算器用途的话, 可以考虑用字符串替换+正则过滤来处理。
    如果只有用户能看到这些代码,恶意就恶意呗,他只能恶到自己。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1022 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 21:33 · PVG 05:33 · LAX 13:33 · JFK 16:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.