写在前边


这是我第一在CSDN发布博客。

在一个月左右的前端基础学习之后,我开始着手自己做一个简单的计算器。

目前实现的功能:加减乘除四则运算,带括号的运算,小数点计算,黑暗/白天模式切换,响应式

UI设计


白天模式

暗黑模式

小窗口

       在设计上参考了最近比较火的新拟态设计(并不是特别正规),其实现大致分为

1.凸起按钮

利用右下角的深色阴影和左上角的浅色阴影实现

2.凹陷按钮

利用内部的(inset)左上角深色阴影和内部的右下角浅色阴影实现

3.圆形凸起/凹陷

类似于大头针的感觉的凸起(凹陷感觉是个“坑”的样式),在上述基础上添加一个135度的线性渐变

其实设计上就是模拟左上角的打光实现的阴影让视觉上看起来是凸起或凹陷的,这里有一个网站https://neumorphism.io/#ffffff可以在线生成,弄懂原理之后并不难

代码


接下来是重点的代码部分

目录结构(有能看出来是什么IDE的大神嘛)

calculator.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>计算器</title><link rel="stylesheet" href="css/whiteCal.css" id="mode"><link rel="stylesheet" href="css/calculator.css" ><!--宽度大于1019px生效--><link rel="stylesheet"  href="css/calculator-mini.css"><!--宽度小于1019px生效-->
</head>
<body>
<div id="changeButtonDiv"><button type="button" id="change" onclick="changeStyle()"></button>
</div><div id="content"><div id="windowArea"><div id="answerArea"></div></div><div id="operatorArea"><div id="funcArea"><div id="clear" class="button">C</div><div id="brackets" class="button">()</div><div id="backspace" class="button">←</div></div><div id="numbersArea"><button type="button" class="button num">1</button><button type="button" class="button num">2</button><button type="button" class="button num">3</button><button type="button" class="button num">4</button><button type="button" class="button num">5</button><button type="button" class="button num">6</button><button type="button" class="button num">7</button><button type="button" class="button num">8</button><button type="button" class="button num">9</button><button type="button" class="button num">.</button><button type="button" class="button num" style="width: 160px;">0</button></div><div id="computeArea"><div class="button com" style="clear:left">+</div><div class="button com" style="clear:left">-</div><div class="button com" style="clear:left">*</div><div class="button com" style="clear:left">/</div></div><button class="button" id="compute">=</button></div>
</div><script src="js/calculator.js"></script></body>
</html>

html的部分没什么好说的,基础的结构,变量名起的比较规范,不需要注释应该也可以看懂

css

css部分采用分离式写法,将有关颜色的部分与大小位置等样式分开写,方便颜色的切换。

实际上还有一种颜色切换的方式,给所有涉及颜色的元素添加一个统一的类,用js控制,黑色添加black类,白色添加white类,再在对应的类下写相应的样式(想法来自后来写的翻页时钟)

正常模式下的calculator.css(只包含大小和样式)

@media  screen and (min-width:1019px) {* {margin: 0;padding: 0}/***计算器面板*/#content {width: 1000px;height: 700px;margin: 20px auto;text-align: center;border-radius: 50px;}/***视窗区*/#windowArea {float: left;width: 500px;height: 700px;box-sizing: border-box;border-radius: 50px 20px 20px 50px;}/***视窗面板*/#answerArea {width: 450px;   height: 650px;margin: 20px;   box-sizing: border-box;border-radius: 50px;word-break: break-all;font-size: 50px;      padding-top: 30px;}/***操作区面板*/#operatorArea {float: right;width: 500px;height: 700px;box-sizing: border-box;border-radius: 20px 50px 50px 20px;font-size: 30px;}/***功能区*/#funcArea {float: left;margin-left: 40px;margin-top: 50px;width: 400px;}/***符号区*/#computeArea {float: left;}/***数字区*/#numbersArea {float: left;margin-left: 40px;width: 290px;height: 380px;border-radius: 100px;}/***按键*/.button {float: left;width: 70px;height: 70px;line-height: 70px;margin-left: 20px;margin-top: 20px;text-align: center;font-size: 20px;border-radius: 100px;border: none;outline: none;}/*计算符号*/.com {border-radius: 15px;}/*退格键*/#backspace {width: 170px;margin-left: 30px;}/*计算键*/#compute {position: relative;width: 370px;font-size: 50px;color: greenyellow;margin-left: 60px;border-radius: 15px;}/***更改主题按钮*/#changeButtonDiv {position: absolute;left: 50px;top: 50px;width: 30px;height: 60px;background: white;border: 5px solid #7a7a7a;border-radius: 20px;}#change {position: absolute;top: 0;width: 30px;height: 30px;border-radius: 100px;border: none;background: linear-gradient(135deg, #d6d6d6, #c6c6c6);box-shadow: 1px 1px 3px #3a3a3a;outline: none;}#change:hover {cursor: pointer;}
}

小窗口下的calculator-mini.css(不是针对小屏设备的css,仅仅是在缩小了屏幕窗口后的响应式适配)

@media  screen and (max-width:1019px) {* {margin: 0;padding: 0;}/***计算器面板*/#content {width: 50%;height: 650px;margin: 20px auto;text-align: center;border-radius: 50px;}/***视窗区*/#windowArea {float: left;width: 100%;height: 100px;box-sizing: border-box;border-radius: 50px;overflow: hidden;}/***视窗面板*/#answerArea {width: 90%;     height: 80px;margin-left:5%;     margin-top: 5px;word-break: break-all;box-sizing: border-box;border-radius: 50px;font-size: 30px;      padding-top:5px;}/***操作区面板*/#operatorArea {float: right;width: 100%;    height: 550px;box-sizing: border-box;border-radius: 50px;font-size: 30px;overflow: hidden;}/***功能区*/#funcArea {float: left;margin-left: 40px;width: 400px;}/***符号区*/#computeArea {float: left;}/***数字区*/#numbersArea {float: left;margin-left: 40px;width: 290px;height: 380px;border-radius: 100px;}/***按键*/.button {float: left;width: 70px;height: 70px;line-height: 70px;margin-left: 20px;margin-top: 20px;text-align: center;font-size: 20px;border-radius: 100px;border: none;outline: none;}/*计算符号*/.com {border-radius: 15px;}/*退格键*/#backspace {width: 170px;margin-left: 30px;}/*计算键*/#compute {position: relative;width: 50%;       height: 50px;font-size: 50px;    line-height: 50px;color: greenyellow;margin-left: 25%;  margin-top: 5px;border-radius: 15px;}/***更改主题按钮*/#changeButtonDiv {position: absolute;left: 50px;top: 50px;width: 30px;height: 60px;background: white;border: 5px solid #7a7a7a;border-radius: 20px;}#change {position: absolute;top: 0;width: 30px;height: 30px;border-radius: 100px;border: none;background: linear-gradient(135deg, #d6d6d6, #c6c6c6);box-shadow: 1px 1px 3px #3a3a3a;outline: none;}#change:hover {cursor: pointer;}
}

白天模式的white.css

#content{border:5px solid rgba(255, 255, 255, 0.72);box-shadow: 5px 5px 15px #cbcbcb, -5px -5px 15px #f7f7f7;
}
/**
*视窗区*/
#windowArea{border:5px solid white;box-shadow: inset 5px 5px 15px #cacaca, inset -5px -5px 15px #6b6b6b;background: linear-gradient(135deg, #ffffff, #f1f3f1);
}
/**
*操作区*/
#operatorArea{border:5px solid white;box-shadow: inset 5px 5px 10px #d0d0d0, inset -5px -5px 15px #989898;background: linear-gradient(135deg, #ffffff, #f1f3f1);
}
/**
*视窗面板*/
#answerArea{border: 1px solid #f4f4f4;background: white;color: coral;
}
/**
*按键*/
.button{color: coral;   background: white;box-shadow: 3px 3px 5px#989898,-3px -3px 5px #d0d0d0;
}
/*按钮悬停样式*/
.button:hover{background: coral;  color: white;box-shadow: inset 3px 3px 5px #9a4d2f,inset -3px -3px 5px #ffcc99;
}
#compute{background: #146dff;
}
/*计算键鼠标悬停颜色从左到右填充样式*/
#compute:after,#compute:before{content:'';position: absolute;     left:0;     top:0;width: 0;   height: 100%;background: #146dff;z-index: -2;border-radius:12px ;
}
#compute:hover{z-index: 1;      background: transparent;    color: #146dff;}
#compute:before{transition: all 0.5s;background: aquamarine;box-shadow: inset 3px 3px 5px #59b294,inset -3px -3px 5px #d0eaff;z-index: -1;
}
#compute:hover:after,#compute:hover:before{width: 100%;}

暗黑模式dark.css

body{background: #272727}
#content{border:5px solid #272727;box-shadow: 5px 5px 15px black, -5px -5px 15px #232323;background: #272727;
}
/**
*视窗区*/
#windowArea{border:5px solid #272727;box-shadow: inset 5px 5px 15px black, inset -5px -5px 15px #232323;background: linear-gradient(225deg, #1f1f1f, #101010);
}
/**
*操作区*/
#operatorArea{border:5px solid #161616;box-shadow: inset 5px 5px 10px #393439, inset -5px -5px 15px #232323;background: linear-gradient(135deg, #262626, #272727);
}
/**
*视窗面板*/
#answerArea{border: 1px solid #161616;background: #272727;color: #abaeab;
}
/**
*按键*/
.button{color: #abaeab;   background: #272727;box-shadow: 3px 3px 5px #232323,-3px -3px 5px #393439;
}
/*按钮悬停样式*/
.button:hover{background:#272727;  color: #abaeab;box-shadow: inset 3px 3px 5px #232323,inset -3px -3px 5px #393439;
}
#compute{background: #272727;
}
/*计算键鼠标悬停颜色从左到右填充样式*/
#compute:after,#compute:before{content:'';position: absolute;     left:0;     top:0;width: 0;   height: 100%;background: #272727;z-index: -2;border-radius:12px ;
}
#compute:hover{z-index: 1;      background: transparent;}
#compute:before{transition: all 0.5s;background: #000000;box-shadow: inset 3px 3px 5px #232323,inset -3px -3px 5px #393439;z-index: -1;
}
#compute:hover:after,#compute:hover:before{width: 100%;}

这里多说一句,之前又看过一篇文章,有提到为什么微信迟迟不开放暗黑模式(最近开了,头条的暗黑模式什么时候能上)。

对于我来说,暗黑模式的作用并非在晚上看手机护眼(emmmm...所以近视),只是为了和黑色的果8匹配(奇怪的理由,ios有暗黑模式了,应用也得黑才舒服)。

而实际上,暗黑模式并不仅仅是把界面变黑那么简单。科学研究表明,纯黑色的界面不仅不护眼,更会增加视觉负担,并且使得一些在亮色模式下的阴影、动效等提示用户的功能变得难以察觉。所以实际上应用的暗色模式使用的大多是#191919左右的不同“黑色”(或者说灰色)来凸显阴影、层级以及交互。

同样,这款计算器在配色方面也参考了这一理念(调色让人上瘾)。

calculator.js

/*** 切换日间/夜间模式*/
function changeStyle(){var changeButton=document.getElementById("change");var bgcolor=document.getElementById("changeButtonDiv");var Mode=document.getElementById("mode");var modeStr=Mode.href;var start=modeStr.lastIndexOf("/")+1;var end=modeStr.lastIndexOf(".");var mode=modeStr.slice(start,end);if (mode=="whiteCal"){Mode.href="css/darkCal.css";changeButton.style.top="30px";bgcolor.style.background="aqua";}else {Mode.href="css/whiteCal.css";changeButton.style.top="0";bgcolor.style.background="white";}
}/***计算*/
var button=document.getElementsByClassName("button");
var answerArea=document.getElementById("answerArea");
var expression=[];//存值/*** 判断视窗区是否为空* @returns {boolean}*/
function isEmpty() {return answerArea.innerText == "";
}/*** 为每个按钮添加点击事件并处理*/
for (let i=0;i<button.length;i++){button[i].onclick=handleInput;//添加点击事件//处理输入function handleInput() {var thisValue=this.innerHTML;//被点击的按钮的值(即当前值)var lastValue=expression[expression.length-1];//将栈顶的值保存(即上一次操作的值)// 需要用expression.length 而不是length,length=1if ( !isNaN(thisValue) ) {//如果输入的是数字displayAndSave(thisValue,!isEmpty());//显示值并保存if ( !isNaN(lastValue) )     combineNumber(lastValue,thisValue);//如果上一次输入也是数字则合并else if (lastValue==".")     combineFloat(thisValue);//如果上一次输入了“.",则需要处理小数}else {//输入的不是数字,而是操作符if (!isEmpty()) {switch (thisValue) {case"←" :   backspace();                          break;case "C" :  clear();                                    break;case "()" :  fillBrackets(true);         break;case "=":   cal();                                       break;default  ://如果上一次输入的是数字或是括号,则可以输入+-*/.,否则报错if ( !isNaN(lastValue) || lastValue=="(" || lastValue==")" )  displayAndSave(thisValue,true);else alert("不能连续输入两次运算符!");break;}}else {//只允许先输入括号if (thisValue=="()")     fillBrackets(false);else    alert("上来就想操作?");}}console.log("输入了"+thisValue+"\t上一次输入了:"+lastValue);console.log("表达式:"+expression);}
}/*** 处理连续输入的数字* @param lastValue* @param thisValue*/
function combineNumber(lastValue,thisValue) {var temp="";temp = lastValue + thisValue;//把上一次和这一次的值合成一个字符串expression.pop();//弹出thisValueexpression.pop();//弹出lastValueexpression.push(temp);//压入新数console.log("整数处理后的expression:"+expression);
}/*** 处理小数* @param thisValue*/
function combineFloat(thisValue) {var lastLastValue=expression[expression.length-3];//小数点前的数字// -1是小数点后的数字,-2是小数点,-3是小数点前的数字var temp="";console.log("小数点前的数字:"+lastLastValue);temp=temp+lastLastValue+"."+thisValue;expression.pop();//弹出小数点后的数字expression.pop();//弹出小数点expression.pop();//弹出小数点钱的数字expression.push(temp);//压入合并后的小数console.log("小数处理后的expression:"+expression);
}/*** 清屏*/
function clear() {answerArea.innerHTML="";expression=[];
}/*** 退格*/
function backspace() {//表达式的长度为1则清零,否则将截取表达式的第一位到倒数第二位,实现退格answerArea.innerText=answerArea.innerText.length==1 ? "" : answerArea.innerText.substr(0,answerArea.innerText.length-1);expression.pop();//删除最末尾的值
}/*** 填写括号*/
function fillBrackets(isSave) {//没左括号先写左括号,有则写右括号if ( answerArea.innerText.indexOf("(")==-1 )  displayAndSave("(",isSave);else    displayAndSave(")",isSave);
}/*** 将按下的按钮的值显示在answerArea中,并存入expression[]* @param value 显示的值, isSave 是否保留AnswerArea中的值 : true->在其后显示;false->替换原有值*/
function displayAndSave(value , isSave) {expression.push(value);if (isSave)     answerArea.innerHTML +=value;else    answerArea.innerHTML =value;
}/***计算*/
function cal() {if (isEmpty()){alert("先按=?不对吧!")}else {var preExpression=[];var result=[];//计算结果var i=0;var top=0;var sub=0;try{console.log("开始计算");preExpression=handleExpression();//处理表达式var length=preExpression.length;var value=0;for ( let i=0 ; i<length ; i++   ){//从左向右遍历if ( !isNaN(preExpression[i]) ){//读取到数字根据其中是否有小数点判断转换类型后压入resultif ( preExpression[i].indexOf(".")==-1 )      value=parseInt(preExpression[i]);else    value=parseFloat(preExpression[i]);result.push(value);}else {//读取到运算符top=result.pop();//栈顶元素sub=result.pop();//次顶元素result.push( compute(top,sub,preExpression[i]) );}}answerArea.innerHTML+="<p>="+result[0]+"</p>"}catch {alert("发生了问题,没得到答案,再来一次试试?");clear();}}
}/*** 计算运算符优先级* @param thisOperator* @returns {number} 乘除返回2,加减返回1*/
function computePriority(thisOperator) {if ( thisOperator=="*" || thisOperator=="/" )  return 2;else if ( thisOperator=="+" || thisOperator=="-" ) return 1;else return 0;
}/*** 处理表达式* @returns {[]}*/
function handleExpression() {console.log("开始转换表达式。原表达式为:"+expression+",长度为:"+expression.length);var preExp=[];var operator=[];//将中缀表达式转换为前缀表达式for ( let i=expression.length-1 ; i>=0 ; i-- ){//从右到左遍历expressionif ( !isNaN(expression[i]) ){//读取到操作数,入栈preExp.push(expression[i]);console.log("temp="+preExp+"\t operator="+operator);}else {//读取到非数字字符if ( operator=="" || operator[operator.length-1]==")" ){//operator为空或栈顶为 )右括号 则直接入栈operator.push(expression[i]);}else {//operator不为空且栈顶不是)右括号if ( expression[i]=="(" ){//是左括号while ( operator[operator.length-1] !== ")" ){//将operator栈顶元素压入temp直到遇到 )右括号preExp.push(operator.pop());}operator.pop();//弹出)右括号}else if ( expression[i]==")" ){//是右括号,直接入栈operator.push(expression[i]);}else {//是运算符while ( operator[operator.length-1] !== ")" || operator.length>0){//将这个运算符与栈顶的运算符比较优先级,直到栈顶的不是运算符为止if (computePriority(expression[i]) >= computePriority(operator[operator.length - 1])) {operator.push(expression[i]);//如果当前运算符优先级大于等于栈顶运算符,直接入栈break;}else {//如果当前运算符优先级低于栈顶运算符,则将栈顶运算符弹出,然后压入temppreExp.push(operator.pop());}}}}}}while( operator.length>0 ){//将operator所有元素弹出并压入preExp中preExp.push(operator.pop());//得到是反向的前缀表达式,在计算结果时从左向右遍历即可}console.log("处理后的表达式="+preExp);return preExp;
}/*** 中间计算* @param top   栈顶元素* @param sub   次顶元素* @param operator  运算符* @returns {number}    结果*/
function compute(top,sub,operator) {var result=0;switch (operator) {case "+" :  result=top+sub ;    break;case "-"  :  result=top-sub ;     break;case "*"  :  result=top*sub ;     break;case "/"  :try{result=top/sub ;}catch (e) {console.log(e.error);}break;}result=result.toFixed(5);return result;
}

这是总体的js,全部的代码就是这些了,接下详细解释js中的算法

算法


切换主题

思路

切换主题的实现方式其实没什么好说的,原理就是获取按钮对应的DOM节点,绑定点击事件(onclick),当点击时判断当前的css文件是white还是dark,从而切换主题,同时将自身位置下移,并把背景颜色设为 aqua。

在获取当前css的路径名后需要做一个简单的处理,即截取最后一个“/”和最后一个“.”中间的字符串(可以在获取之后console.log一下css路径,很长的一串,之后就知道该怎么截取了)

对应的代码段

/*** 切换日间/夜间模式*/
function changeStyle(){var changeButton=document.getElementById("change");var bgcolor=document.getElementById("changeButtonDiv");var Mode=document.getElementById("mode");var modeStr=Mode.href;var start=modeStr.lastIndexOf("/")+1;var end=modeStr.lastIndexOf(".");var mode=modeStr.slice(start,end);if (mode=="whiteCal"){Mode.href="css/darkCal.css";changeButton.style.top="30px";bgcolor.style.background="aqua";}else {Mode.href="css/whiteCal.css";changeButton.style.top="0";bgcolor.style.background="white";}
}

处理输入

思路

要想做好计算器,第一步就是知道用户输入了什么,同时反馈出相应的处理。

这是我在GoodNotes上写的一个简单的实现方法,原理是在每一次输入时对比上一次输入,得到以下几种情况:

1.这一次是数字

(1).上一次是数字 => 将上一次输入的数字弹出,与这次输入拼接成一个数字字符串(也可以将第一个*10再加第二个数,将字符串变成数字),再压入回表达式(栈)。

(2).上一次是运算符 或 ()=> 压入表达式。

(3).上一次是小数点. => 将上上次的数字弹出(由于(1)的处理,上上次的数字一定是处理成多位数的结果),弹出的数字与小数点和这一次的输入合并后压入表达式。

2.这一次是运算符

(1).上一次是运算符 => 不允许输入(也可以单独考虑 - 这一特殊情况,允许 + 后跟随 - ,将其视为负数 ,但我没这么做)。

(2).上一次是数字或() =>  压入表达式。

3.特殊的()

由于布局考虑,只给了()一个按钮的位置,那么如何实现输入括号呢?

解决方法是判断目前的表达式是否有( ,有就写),没有写(。得到的结果是只能写一对()。最好的方式其实是()分别占两个按键,或者说给一个变量flag,起始为true,判断flag为true时写( 并把flag置为false,flag为false时输出 ),再将flag设为true。后续改进。

输入数字、小数点、括号对应的代码段

/*** 处理连续输入的数字* @param lastValue* @param thisValue*/
function combineNumber(lastValue,thisValue) {var temp="";temp = lastValue + thisValue;//把上一次和这一次的值合成一个字符串expression.pop();//弹出thisValueexpression.pop();//弹出lastValueexpression.push(temp);//压入新数console.log("整数处理后的expression:"+expression);
}
/*** 处理小数* @param thisValue*/
function combineFloat(thisValue) {var lastLastValue=expression[expression.length-3];//小数点前的数字// -1是小数点后的数字,-2是小数点,-3是小数点前的数字var temp="";console.log("小数点前的数字:"+lastLastValue);temp=temp+lastLastValue+"."+thisValue;expression.pop();//弹出小数点后的数字expression.pop();//弹出小数点expression.pop();//弹出小数点钱的数字expression.push(temp);//压入合并后的小数console.log("小数处理后的expression:"+expression);
}
/*** 填写括号*/
function fillBrackets(isSave) {//没左括号先写左括号,有则写右括号if ( answerArea.innerText.indexOf("(")==-1 )  displayAndSave("(",isSave);else    displayAndSave(")",isSave);
}

对于特殊操作的处理

特殊操作是指退格、清屏

退格

退格的思路是 判断当前表达式长度是否为1,为1则清零,否则pop()并截取字符串到倒数第二位

/*** 退格*/
function backspace() {//表达式的长度为1则清零,否则将截取表达式的第一位到倒数第二位,实现退格answerArea.innerText=answerArea.innerText.length==1 ? "" : answerArea.innerText.substr(0,answerArea.innerText.length-1);expression.pop();//删除最末尾的值
}

清屏

清屏的思路是 将表达式数组清空,同时将视窗区清空

/*** 清屏*/
function clear() {answerArea.innerHTML="";expression=[];
}

处理表达式

https://blog.csdn.net/antineutrino/article/details/6763722这篇文章详细讲述了前、中、后缀表达式以及转换的算法。

这是我在GoodNotes上的笔记。我的计算器使用的是前缀表达式(emmm...处理之后的结果是后缀,再将元素弹出到新栈就是前缀,前缀计算时需要从右到左遍历表达式,我从左到右遍历的,所以结果一样,看不懂请忽略这句非人话)。大体思路就是蓝色字体所写的。

接下来通过1+((2+3)*4)-5这个例子解释算法:

1.准备两个栈。 放中间结果的temp[] 和 放运算符的operator[]

2.从右到左读取表达式,也就是 5 - )4 *  )3 + 2 ( ( + 1

3.接下来判断

(1)遇到5 => temp[] 。 此时 temp=[5] , operator=[]

(2)遇到- => operator 为空 => operator[] 。 此时temp=[5] , operator=[-]

(3)遇到)=> operator[] 。 此时temp=[5] , operator=[ - , ) ]

(4)遇到4 => temp[] 。此时temp=[5 , 4 ] , operator=[ - , ) ]

(5)遇到* =>operator[length-1]为)=> operator[] 。 此时temp=[ 5 , 4 ] , operator=[ - , ) , * ]

(6)遇到)=> operator[] 。 此时temp=[ 5 , 4 ] , operator=[ - , ) , * , ) ]

(7)遇到3 => temp[] 。此时temp=[5 , 4 , 3 ] , operator=[ - , ) , * , ) ]

(8)遇到+ => operator[length-1]为)=> operator[] 。 此时temp=[ 5 , 4 , 3 ] , operator=[ - , ) , * , ) , + ]

(9)遇到2 => temp[] 。此时temp=[ 5 , 4 , 3 , 2 ] , operator=[ - , ) , * , ) , + ]

(10)遇到( => pop + ->temp[] => pop )  。此时 temp=[ 5 , 4 , 3 , 2  , + ] , operator=[ - , ) , *  ]

(11)遇到( => pop * ->temp[] => pop )  。此时 temp=[ 5 , 4 , 3 , 2  , +  , * ] , operator=[ - ]

(12)遇到+ => operator[length-1]为+,优先级相同=> operator[] 。 此时temp=[ 5 , 4 , 3 , 2  , +  , * ] , operator=[ - , + ]

(13)遇到1 => temp[] 。此时temp=[ 5 , 4 , 3 , 2  , +  , *  , 1 ] , operator=[ - ,  + ]

(14)operator[] => temp[] 。 temp=[ 5 , 4 , 3 , 2  , +  , *  , 1 , + , - ]

一共14步,其实不难,但写起来有些复杂(很磨耐性)。

对应的代码段

/*** 处理表达式* @returns {[]}*/
function handleExpression() {console.log("开始转换表达式。原表达式为:"+expression+",长度为:"+expression.length);var preExp=[];var operator=[];//将中缀表达式转换为前缀表达式for ( let i=expression.length-1 ; i>=0 ; i-- ){//从右到左遍历expressionif ( !isNaN(expression[i]) ){//读取到操作数,入栈preExp.push(expression[i]);console.log("temp="+preExp+"\t operator="+operator);}else {//读取到非数字字符if ( operator=="" || operator[operator.length-1]==")" ){//operator为空或栈顶为 )右括号 则直接入栈operator.push(expression[i]);}else {//operator不为空且栈顶不是)右括号if ( expression[i]=="(" ){//是左括号while ( operator[operator.length-1] !== ")" ){//将operator栈顶元素压入temp直到遇到 )右括号preExp.push(operator.pop());}operator.pop();//弹出)右括号}else if ( expression[i]==")" ){//是右括号,直接入栈operator.push(expression[i]);}else {//是运算符while ( operator[operator.length-1] !== ")" || operator.length>0){//将这个运算符与栈顶的运算符比较优先级,直到栈顶的不是运算符为止if (computePriority(expression[i]) >= computePriority(operator[operator.length - 1])) {operator.push(expression[i]);//如果当前运算符优先级大于等于栈顶运算符,直接入栈break;}else {//如果当前运算符优先级低于栈顶运算符,则将栈顶运算符弹出,然后压入temppreExp.push(operator.pop());}}}}}}while( operator.length>0 ){//将operator所有元素弹出并压入preExp中preExp.push(operator.pop());//得到是反向的前缀表达式,在计算结果时从左向右遍历即可}console.log("处理后的表达式="+preExp);return preExp;
}

计算

最后一步,也是最关键的一步就是计算了。

从左到右读取表达式,遇到操作数将其压入result[]中,读取到运算符就将栈顶和次顶元素进行相应的运算,其中可以判断一下表达式,有小数点将字符串parseFloat ,没有就parseInt。由于JavaScript的浮点数计算不精确,所以在小数计算中将其结果toFixed(5),保留5位小数

计算对应的代码

按下=触发事件,开始计算

/***计算*/
function cal() {if (isEmpty()){alert("先按=?不对吧!")}else {var preExpression=[];var result=[];//计算结果var i=0;var top=0;var sub=0;try{console.log("开始计算");preExpression=handleExpression();//处理表达式var length=preExpression.length;var value=0;for ( let i=0 ; i<length ; i++   ){//从左向右遍历if ( !isNaN(preExpression[i]) ){//读取到数字根据其中是否有小数点判断转换类型后压入resultif ( preExpression[i].indexOf(".")==-1 )      value=parseInt(preExpression[i]);else    value=parseFloat(preExpression[i]);result.push(value);}else {//读取到运算符top=result.pop();//栈顶元素sub=result.pop();//次顶元素result.push( compute(top,sub,preExpression[i]) );}}answerArea.innerHTML+="<p>="+result[0]+"</p>"}catch {alert("发生了问题,没得到答案,再来一次试试?");clear();}}
}

中间计算对应的代码段

/*** 中间计算* @param top   栈顶元素* @param sub   次顶元素* @param operator  运算符* @returns {number}    结果*/
function compute(top,sub,operator) {var result=0;switch (operator) {case "+" :  result=top+sub ;    break;case "-"  :  result=top-sub ;     break;case "*"  :  result=top*sub ;     break;case "/"  :try{result=top/sub ;}catch (e) {console.log(e.error);}break;}result=result.toFixed(5);return result;
}

在处理除法时可以单独考虑除数为零的情况然后throw抛出错误,我这里直接try catch了

其他的想判断运算符优先级,判断视窗区是否为空的函数就不赘述了。

总结


主观来说,这个计算器还阔以,基本完成作为计算器应该做的事,而其实比起算法让我更感兴趣的是界面的设计。

客观来说,这个计算器比较草率,功能少,缺少更完善的处理以及更丰富的功能,同时在应对用户不同的输入情况没有继续细分,加深了用户的学习成本,希望日后有时间可以改善。

总的说来,这次动手做个计算器也是对于阶段性学习的一个总结,书本和IDE上亲手打的代码终究还是千差万别,错误也往往存在于细节之中,而只有亲手去做,才能获得更好的理解。(不得不吐槽学校的教学方式,老师一个劲将,学生也没什么实践的机会,实践课老师也就坐着像看晚自习......)

我在CSDN的第一篇博客——js实现简单计算器相关推荐

  1. 我在CSDN的第一篇博客-iOS开发-关于Debug的一些技巧(NSLog方面)

    唠叨几句 本来想写点感言的,不过想了想觉得有点儿矫情,还是算了.开博客原因很简单,就是想锻炼一下自己表达能力,并且总结一些需要积累的东西. 第一篇博客,还是写点有用的东西吧. 刚刚看到的一篇关于Deb ...

  2. 我的第一篇博客:如何简单快速地解决Xshell无法打开 ,缺少 XX.dll文件的问题

    一.原因: VC++相关组件缺失 二.解决方法: 1.安装360,它会自动安装VC++解决这个问题(可以,但不推荐,360这个软件不是很友好,懂的都懂) 2.用DirectX修复工具强力版本(推荐方法 ...

  3. 第一篇博客------自我介绍

    目录 自我介绍 编程目标 如何编程 希望进入的公司 自我介绍:       Hello!!!我是一名即将步入大二的计算机小白. 小白 姓名:###(三个字) 性别:男 年龄:大二(大概也就0--100 ...

  4. C博客作业00--我的第一篇博客

    这个作业属于哪个班级 C语言–网络2011/2012 这个作业的地址 C博客作业00–我的第一篇博客 这个作业的目标 学习Makdown语法,对本专业及C语言课程有个简单了解,记录自学慕课视频的笔记. ...

  5. 启航——我的第一篇博客

    简单的自我介绍: 本人目前就读于西安科技大学软件工程专业,大二,下学期大三.惭愧的是尽管很早之前就注册了csdn,但至今才发第一篇博客(以后一定多多发表,与大家进行共勉)虽然现在才发表博客,但并不代表 ...

  6. Healer的第一篇博客

    1.自我介绍 Healer是来自双非一本弱校的小白一枚,说不上对编程感兴趣但也绝对不会排斥编程.但是既然选择了计算机专业,那呆瓜就有信心就要搞好它,只要坚持下去,Healer一定可以从小白变成大牛! ...

  7. 2020年我的第一篇博客日报

    博客日报缘由 谈起博客我们常想到CSDN.博客园.github等 谈起日报.周报我们自然想到了,工作工作工作... 但是谈起博客日报,那又是什么呢? "博客日报"不知道有没有这个词 ...

  8. 2017年终总结——第一篇博客开端

    2017的总结: 2017年过去了,真的好快,可以清晰的记得我去年除夕的晚上我干了啥.但是2017年也是我改变比较大的一年,何出此言呢,总结了几点: 1.变的更加稳重成熟了,这种感觉的由来,其实是有时 ...

  9. 【我的第一篇博客】——上个月学习总结(4月)

    大家好,我是monitor_sun 目录 前言 学习月总结 1.复盘上个月(4月)的学习成果 2.在本月/季度的学习过程中遇到的难题有哪些,是如何解决的,从中学到了什么? 3.有什么事情是自己一直想做 ...

最新文章

  1. 再见了,Windows AutoRun!
  2. php跟web前端的区别,php与javascript的区别是什么?
  3. 使用 Android NDK 的交叉编译工具链移植 C/C++ 项目到安卓平台
  4. 中国火电设备市场发展方向与投资策略研究报告2022版
  5. 曼哈顿距离java实现_基于javascript实现获取最短路径算法代码实例
  6. Linux服务器开发之:stat(),fstat(),lstat()详细介绍+案例演示
  7. 将jOOQ与Spring结合使用:代码生成
  8. c语言中的所有代码大全,C语言库函数代码大全
  9. Lady Bird
  10. html打开软件连接的代码,《前端开发从零学起》Lesson.7 HTML中超链接的使用方法...
  11. PHP 实现-多线程编程
  12. 《人月神话》读后感与读书笔记
  13. amx-104 r-java_AMX-104 R·贾贾
  14. kotlin data class 遇到的坑
  15. 获取分时数据,日k数据(A股,港股,美股)
  16. andorid Telephony 整体介绍
  17. 刘道成 mysql 课件_燕十八公益讲堂mysql.ppt
  18. 【2022-8-27完美世界】完美世界图像算法岗笔试
  19. 如何使用GMAP/GSNAP进行转录组序列比对
  20. DragonBall

热门文章

  1. IPV6 邻居发现协议(NDP)
  2. 【源码】loess method扩展算法仿真
  3. 湖南天才少女姚婷:刚毕业就被华为156万年薪邀请,来历不简单
  4. 汉光武帝刘秀--昆阳之战
  5. python 保存图片
  6. 发现美,创造美,拥有美^_^.
  7. 爬取起点小说网免费小说
  8. 自动爬取微博热门评论和点赞数并存为EXCEL文件(python2)
  9. SAS系统从入门到放弃?不能放弃,它是数据科学家必备技能
  10. 甜心奶酪用英文怎么说_您组织中没有人会碰到什么奶酪,更不用说动弹了?