如何不用`eval`实现前端的自由输入的计算器? - V2EX
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 6646 次点击
    这是一个创建于 4608 天前的主题,其中的信息可能已经有所发展或是发生改变。
    要实现的效果:在页面上有一个`<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     Solana     2220 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 32ms UTC 16:07 PVG 00:07 < href="/worldclock#lax">LAX 09:07 JFK 12:07
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86