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类名的(有些直接用标签名获取),可以直接用querySelectorquerySelectorAll(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 });

 

第六步:?

  本案例还有可以继续改进的地方,例如用模块化对函数进行再次封装,添加键盘事件,对小数的计算进行改进,等等

posted @ 2019-08-31 22:37  且听风吟720  阅读(393)  评论(0编辑  收藏  举报