要求:
响应用户对数字和算术操作符按钮的操作,记录并显示用户通过按钮输入的算术表达式(50分)响应用户功能按钮的操作
用户按下“←”按钮,删除当前算术表达式最后一个字符,并更新显示
用户按下“CE”按钮,清除当前算术表达式(20分)用户按下“=”按钮,计算当前表达式的结果并显示
如果,算术表达式非法,弹出警告框提醒用户,并终止计算
效果图:
代码链接:https://github.com/sysuKinthon/Web2.0/tree/master/Web2.0/calculator
经验:
1)其实代码实现不难,而且用了已经不大推崇的eval函数,这个函数会带来代码注入的问题;
2)实现过程中主要想说的是一种思维,因为之前一直都不怎么接触事件处理机制,一直都是用c,c++,java来打代码,所以在写这个的时候有一个致命的思维误区:
//if input the symbol, the reslut should be clear; for(i = 0; i < symbolList.length; ++i) { symbolList[i].onclick = function(event) { //不能使用i来索引对象,因为当Onclick事件发生时,i已经都是20了。 if(flag == true) { expression.value = ""; flag = false; } var content = event.target.innerText; expression.value = expression.value + content; } }
一开始在for循环里面,一上手就想用sybmolList[i]来引用innerText;也就是:
var content = event.target.innerText; 换成 var content = symbolList[i].innerText;
发现我一点击事件就出现越界行为,输出查看i,i的值都是同一个数,这个数决定于window.onload回调函数结束后的i值。要清楚,这是闭包导致的,当我们的DOM加装完毕后,window.onload事件就发动了,并调用事件处理;好像一切都没有问题,但是想想看,当我们点击事件,并触发了symbolList中的对象时,回调了我们设置的函数,此时它引用i的值的话,必然是symbolList.length,因为onload事件已经执行了,但由于闭包,如果我们的点击函数中有引用外部的变量的话,是可以访问到的,在javascript中是采用了垃圾处理机制,闭包包含了外部的活动对象,所以这个对象还没有被销毁,可以被引用。所以我们在程序中是不能直接调用 i来索引事件源的,要使用event.target
拓展:
毕竟利用eval来实现计算会导致一些意想不到的问题,所以就用算法实现了下中缀表达式的计算:参考
关于考虑运算符优先级的问题,也就是什么时候栈里面的运算符应该出栈,主要考虑一个方面就可以了,就是运算符的栈始终要保持栈顶的元素优先级最高,相同优先级的后面进入的优先级高,同时需要考虑对于")"来说,当在遇到"("时,它的优先级是最高的,遇到后,就是最低的了(也就是不要入栈)。
主要的函数代码如下:
1 // calculator the postfix expression 2 function calculate(postfix) { 3 var i, item, result; 4 var num = []; 5 for(i = 0; i < postfix.length; i++) { 6 item = parseFloat(postfix[i]); 7 if(!isNaN(item)) { 8 num.push(item); 9 } else { 10 second = num.pop(); 11 first = num.pop(); 12 switch(postfix[i]) { 13 case '+': 14 result = first + second; 15 num.push(result); 16 break; 17 case '-': 18 result = first - second; 19 num.push(result); 20 break; 21 case '/': 22 result = first / second; 23 num.push(result); 24 break; 25 case '*': 26 result = first * second; 27 num.push(result); 28 break; 29 } 30 //console.log(result); 31 } 32 } 33 result = num.pop(); 34 result = parseFloat(result.toFixed(8), 10); 35 return result; 36 } 37 38 39 // translate the infix expression to the postfix expression 40 function toPostfix(infix) { 41 //split the input to the token 42 var regex = /(\(|\)|\/|\*|\-|\+)/; 43 var array= infix.split(regex); 44 var i; 45 var postfix = []; 46 var symbol = []; 47 // remove the empty item 48 for(i = 0; i < array.length; i++) { 49 if(array[i] == "") 50 array.splice(i, 1); 51 } 52 53 for(i = 0; i < array.length; i++) { 54 var item = array[i]; 55 if(!isNaN(parseFloat(item))) { 56 postfix.push(item); 57 } else { 58 while(symbol.length) { 59 if(compare(item, symbol[symbol.length-1])) { //if the item is greater 60 break; 61 } else { 62 postfix.push(symbol.pop()); 63 /*if(postfix[postfix.length-1] == "(") 64 console.log(1);*/ 65 } 66 } 67 if(item == ")") { 68 symbol.pop(); //pop "(" 69 } else { 70 symbol.push(item); 71 } 72 } 73 } 74 while(symbol.length) { 75 if(symbol) { 76 postfix.push(symbol.pop()); 77 } 78 } 79 return postfix; 80 } 81 82 function compare(item, top) { 83 if(item.match(/\)/) && top.match(/\(/)) 84 return true; 85 if(item.match(/\(/) || top.match(/\(/)) { 86 return true; 87 } 88 if(item.match(/\*|\//) && top.match(/\+|\-/)) { 89 return true; 90 } 91 //console.log(item); 92 return false; 93 }