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

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

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

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

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

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

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

现在看来我搜索能力不行,之前搜索了很久都没有“现成 js 库”。现在有楼上几位的网址就知道其实是真的有的。
middleware
2013-03-02 17:50:04 +08:00
你不用寫一個完全的編譯器,只是一個簡單的 parser,大約四五條 BNF 語法,生成 AST 之後再從葉子節點開始求值。
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
2013-03-03 12:33:01 +08:00
1.大段参数的效率比较慢。
效率这种事还是要测试才知道。如果慢得可以接受的话,就没问题。
2.变量作用域默认(好像)是全局作用域。
你在eval加个闭包不就没事了。
3.动态内容可能引入精心构造的恶意代码; 如果只是做计算器用途的话, 可以考虑用字符串替换+正则过滤来处理。
如果只有用户能看到这些代码,恶意就恶意呗,他只能恶到自己。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/61700

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX