JS简易计算器——代码优化
这里用一个简易计算器的案例,来说明代码的一种优化思路和具体方法
结构和样式
先放上该项目的HTML和CSS部分
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 7 <title>简易计算器</title> 8 <style> 9 body{ 10 background:#777777; 11 } 12 section{ 13 width:500px;height:300px; 14 position:absolute;top:50%;left:50%; 15 margin-top:-150px;margin-left:-250px; 16 border:1px dashed black; 17 font-size:20px; 18 } 19 p{ 20 text-align:center; 21 margin-top:50px; 22 } 23 button{ 24 width:40px;height:40px; 25 } 26 </style> 27 </head> 28 <body> 29 <section> 30 <p> 31 <input type="text" class="input_forward" value="0"> 32 <span class="symbol">+</span> 33 <input type="text" class="input_backward" value="0"> 34 <span>=</span> 35 <span class="result">0</span> 36 </p> 37 <p> 38 <button title="add">+</button> 39 <button title="subtract">-</button> 40 <button title="multiply">×</button> 41 <button title="divide">÷</button> 42 </p> 43 </section> 44 </body> 45 </html>
效果图如下(关注的是实际应用效果,因此CSS样式不做太多调整):
第一步:实现计算器效果
最开始先实现效果,用最简单的思路即可(目的是先实现,先完成后完善):
①获取元素:这里大多元素都是有独立的class类名的(有些直接用标签名获取),可以直接用querySelector和querySelectorAll(button按钮)获取
②封装函数:加减乘除,通过获取的元素value,计算后改变result的值,同时改变符号位
③绑定事件:给每一个button绑定事件,在点击时执行某一函数
这里直接把结构、样式和行为分离,不在HTML标签内部添加JS事件,而是在script标签中绑定事件
JS部分的代码如下:
1 //获取所有元素 2 var num_forward = document.querySelector(".input_forward"), 3 num_backward = document.querySelector(".input_backward"), 4 symbol = document.querySelector(".symbol"), 5 result = document.querySelector(".result"), 6 btns = document.querySelectorAll("button"); 7 //封装所有函数 8 function addHandler(){ 9 var num1 = num_forward.value, 10 num2 = num_backward.value; 11 symbol.innerText = "+"; 12 result.innerText = +num1 + +num2;//这里是为了防止被当成字符串拼接 13 } 14 function subtractHandler(){ 15 var num1 = num_forward.value, 16 num2 = num_backward.value; 17 symbol.innerText = "-"; 18 result.innerText = num1 - num2; 19 } 20 function multiplyHandler(){ 21 var num1 = num_forward.value, 22 num2 = num_backward.value; 23 symbol.innerText = "×"; 24 result.innerText = num1 * num2; 25 } 26 function divideHandler(){ 27 var num1 = num_forward.value, 28 num2 = num_backward.value; 29 symbol.innerText = "÷"; 30 result.innerText = num1 / num2; 31 } 32 //绑定事件 33 btns[0].onclick = addHandler; 34 btns[1].onclick = subtractHandler; 35 btns[2].onclick = multiplyHandler; 36 btns[3].onclick = divideHandler;
第二步:修改绑定事件的方法
经过观察得知,其中的绑定事件相当于遍历了btns里的所有元素,因此绑定事件可以用循环来改写
JS代码如下:
1 //获取所有元素 2 var num_forward = document.querySelector(".input_forward"), 3 num_backward = document.querySelector(".input_backward"), 4 symbol = document.querySelector(".symbol"), 5 result = document.querySelector(".result"), 6 btns = document.querySelectorAll("button"); 7 //封装所有函数 8 function addHandler(){ 9 var num1 = num_forward.value, 10 num2 = num_backward.value; 11 symbol.innerText = "+"; 12 result.innerText = +num1 + +num2;//这里是为了防止被当成字符串拼接 13 } 14 function subtractHandler(){ 15 var num1 = num_forward.value, 16 num2 = num_backward.value; 17 symbol.innerText = "-"; 18 result.innerText = num1 - num2; 19 } 20 function multiplyHandler(){ 21 var num1 = num_forward.value, 22 num2 = num_backward.value; 23 symbol.innerText = "×"; 24 result.innerText = num1 * num2; 25 } 26 function divideHandler(){ 27 var num1 = num_forward.value, 28 num2 = num_backward.value; 29 symbol.innerText = "÷"; 30 result.innerText = num1 / num2; 31 } 32 //用循环绑定事件 33 for(var i=0;i<btns.length;i++){ 34 btns[i].onclick = function(){ 35 switch(this.title){ 36 case "add": 37 addHandler(); 38 break; 39 case "subtract": 40 subtractHandler(); 41 break; 42 case "multiply": 43 multiplyHandler(); 44 break; 45 case "divide": 46 divideHandler(); 47 break; 48 } 49 } 50 }
第三步:函数的提取和封装
进一步观察,可以发现加减乘除四个函数里都分成三个部分:
①改变符号位的符号
②计算得出结果
③把计算的结果赋值到结果区域
因此把这三部分提出来,单独封装三个通用的函数,主要是为了减少函数内部的细节暴露,便于阅读函数
该部分修改后,JS代码如下:
1 //获取所有元素 2 var num_forward = document.querySelector(".input_forward"), 3 num_backward = document.querySelector(".input_backward"), 4 symbol = document.querySelector(".symbol"), 5 result = document.querySelector(".result"), 6 btns = document.querySelectorAll("button"); 7 //封装所有函数 8 //计算用的函数 9 function add(num1,num2){ 10 return +num1 + +num2; 11 } 12 function subtract(num1,num2){ 13 return num1 - num2; 14 } 15 function multiply(num1,num2){ 16 return num1 * num2; 17 } 18 function divide(num1,num2){ 19 return num1 / num2; 20 } 21 //修改符号位的函数 22 function changeSymbol(ele){ 23 symbol.innerText = ele; 24 } 25 //修改输出内容的函数 26 function changeResult(ele){ 27 result.innerText = ele; 28 } 29 function addHandler(){ 30 var num1 = num_forward.value, 31 num2 = num_backward.value; 32 changeSymbol("+"); 33 changeResult(add(num1,num2));//这里是为了防止被当成字符串拼接 34 } 35 function subtractHandler(){ 36 var num1 = num_forward.value, 37 num2 = num_backward.value; 38 changeSymbol("-"); 39 changeResult(subtract(num1,num2)); 40 } 41 function multiplyHandler(){ 42 var num1 = num_forward.value, 43 num2 = num_backward.value; 44 changeSymbol("×"); 45 changeResult(multiply(num1,num2)); 46 } 47 function divideHandler(){ 48 var num1 = num_forward.value, 49 num2 = num_backward.value; 50 changeSymbol("÷"); 51 changeResult(divide(num1,num2)); 52 } 53 //用循环绑定事件 54 for(var i=0;i<btns.length;i++){ 55 btns[i].onclick = function(){ 56 switch(this.title){ 57 case "add": 58 addHandler(); 59 break; 60 case "subtract": 61 subtractHandler(); 62 break; 63 case "multiply": 64 multiplyHandler(); 65 break; 66 case "divide": 67 divideHandler(); 68 break; 69 } 70 } 71 }
第四步:变量、函数模块化
目前为止,书写的JS代码中,存有太多的全局变量以及函数,观察可以发现,所有的元素都是最外部框架的子元素,因此可以用一个对象包裹起来
同时,也可以用一个对象把功能近似的函数给包裹起来,用访问对象方法的方式来调用函数
修改后的JS代码如下:
1 //获取所有元素 2 var section = document.querySelector("section"); 3 var calculatorEles = { 4 num_forward :section.querySelector(".input_forward"), 5 num_backward:section.querySelector(".input_backward"), 6 symbol:section.querySelector(".symbol"), 7 result:section.querySelector(".result"), 8 btns:section.querySelectorAll("button") 9 } 10 //封装所有函数 11 //计算用的函数 12 var operation = { 13 add:function(num1,num2){ 14 return +num1 + +num2; 15 }, 16 subtract:function(num1,num2){ 17 return num1 - num2; 18 }, 19 multiply:function(num1,num2){ 20 return num1 * num2; 21 }, 22 divide:function(num1,num2){ 23 return num1 / num2; 24 } 25 } 26 //修改符号位的函数 27 function changeSymbol(ele){ 28 calculatorEles.symbol.innerText = ele; 29 } 30 //修改输出内容的函数 31 function changeResult(ele){ 32 calculatorEles.result.innerText = ele; 33 } 34 function addHandler(){ 35 changeSymbol("+"); 36 changeResult(operation.add(calculatorEles.num_forward.value,calculatorEles.num_backward.value));//这里是为了防止被当成字符串拼接 37 } 38 function subtractHandler(){ 39 changeSymbol("-"); 40 changeResult(operation.subtract(calculatorEles.num_forward.value,calculatorEles.num_backward.value)); 41 } 42 function multiplyHandler(){ 43 changeSymbol("×"); 44 changeResult(operation.multiply(calculatorEles.num_forward.value,calculatorEles.num_backward.value)); 45 } 46 function divideHandler(){ 47 changeSymbol("÷"); 48 changeResult(operation.divide(calculatorEles.num_forward.value,calculatorEles.num_backward.value)); 49 } 50 //用循环绑定事件 51 for(var i=0;i<calculatorEles.btns.length;i++){ 52 calculatorEles.btns[i].onclick = function(){ 53 switch(this.title){ 54 case "add": 55 addHandler(); 56 break; 57 case "subtract": 58 subtractHandler(); 59 break; 60 case "multiply": 61 multiplyHandler(); 62 break; 63 case "divide": 64 divideHandler(); 65 break; 66 } 67 } 68 }
第五步:OCP原则
OCP原则,即开放封闭原则(Open Close Principle),大致上即开放接口,封闭原有的代码(防止修改时原有代码损坏)
到目前为止的代码,尚存在以下问题:
①switch语句,一般不使用switch,因为实际开发时很可能不给源代码,增删改查时无法在原有的switch内部修改
②每个button点击时触发的函数,实际上可以提取出来,直接放到for循环中(修改符号位可以识别button中的innerText,而具体调用的函数名称也在每个button的title里体现了)
③计算方法的问题,既然可以用button的innerText来识别符号位,那也可以用button的title来识别计算方法,并调用这个方法
④添加新方法的问题,类似于①,实际开发中,很可能不会提供源码,那如果需要添加新的计算方法,是无法深入到对象内部进行添加的,因此有必要写一个接口,留给未来添加新方法用
特别注意:这里不适合直接给对象添加新属性来添加新方法,因为不知道对象内部是否已经有同名的属性
综合以上,修改完成的JS代码如下:
1 //获取所有对象 2 var cal = document.querySelector("section"); 3 var calculatorEles = { 4 input_first :cal.querySelector(".input_first"), 5 input_second:cal.querySelector(".input_second"), 6 symbol:cal.querySelector(".symbol"), 7 result:cal.querySelector(".result"), 8 btns:cal.querySelectorAll("button") 9 }; 10 //让元素内所有元素绑定同一事件 11 function each(array,fn){ 12 for(var i=0;i<array.length;i++){ 13 fn(i,array[i]); 14 } 15 } 16 //变更符号位内容 17 function changeSymbol(ele){ 18 calculatorEles.symbol.innerText = ele; 19 } 20 //变更输出内容 21 function changeResult(put){ 22 calculatorEles.result.innerText = put; 23 } 24 //加减乘除(大函数中的小函数封装) 25 var operation = { 26 add:function (num1,num2){ 27 return +num1 + +num2; 28 }, 29 subtract:function(num1,num2){ 30 return num1 - num2; 31 }, 32 multiply:function(num1,num2){ 33 return num1 * num2; 34 }, 35 divide:function(num1,num2){ 36 return num1 / num2; 37 }, 38 //添加一个用来添加新方法的接口 39 addOperation:function(name,fn){ 40 if(!this[name]){ 41 this[name] = fn;//只有当前对象里不含有这个名称的方法时,才会添加方法 42 } 43 return this; 44 } 45 } 46 //运算函数 47 function operate(name,num1,num2){ 48 if(!operation[name]){ 49 throw new Error("没有名为"+name+"的计算方法!"); 50 } 51 operation[name](num1,num2); 52 } 53 each(calculatorEles.btns,function(index,ele){ 54 ele.onclick = function(){ 55 changeSymbol(this.innerText); 56 changeResult(operate(this.title,calculatorEles.input_first.value,calculatorEles.input_second.value)); 57 } 58 });
第六步:?
本案例还有可以继续改进的地方,例如用模块化对函数进行再次封装,添加键盘事件,对小数的计算进行改进,等等