使用JavaScript实现简易计算器(非eval)

本文案例代码:https://pan.baidu.com/s/1c37JFCoYpcdQd12QshI88Q 提取码:flyW
在线浏览地址

一、准备工作

这个文章的示例主要参考Windows系统只带的计算器。你可以按 win键+R 输入calc打开计算器。
关于计算器的布局,这里就不过多的描述,你可以直接下载这个示例中的页面布局。打开后如下效果:

二、js逻辑分析

1.获取点击显示数字

完成准备工作之后,我默认你已经有和我一样的HTML页面了,这个时候,就要开始写js代码了。首先我们需要为所有按钮添加点击事件(js代码都写在js文件夹中的index.js文件)
index.js

// 获取输入框节点
var box1 = document.getElementById("box1");
var box2 = document.getElementById("box2");

// 获取所有按钮集合
var allDiv = document.querySelectorAll("#calculatorKey div");

// 循环按钮集合
for (let i = 0; i < allDiv.length; i++) {
	// 为所有按钮添加点击事件
	allDiv[i].onclick = function () {
	// 获取当前按钮的文本
	var text = this.innerText;

	// 在控制台打印文本
	console.log(text)
	};
}

在上述的代码中,我通过querySelectorAll获取了所有的按钮的集合,并遍历这个集合,为所有节点添加点击事件,为了方便我把输入框节点元素一并获取了,这个时候,如果点击按钮就可以在控制台看见打印的文本信息了。

querySelectorAll() 方法返回文档中匹配指定 CSS 选择器的所有元素,返回 NodeList 对象。
NodeList 对象表示节点的集合。可以通过索引访问,索引值从 0 开始。

我们已经获取到了文本信息,如何把它显示在计算器中呢?
其实很简单,我们只需要把文本赋值给box2的属性innerText即可:

box2.innerText = number

不过不急,如何的赋值我们也要去分析。我们先查看如果按下的是数字(0~9)那么计算器是如何显示的,请仔细看下方动图

我们注意到,计算器的初始值是0,当我们按下1的时候它的值就是1,当我们再按下2的时候,值是12。清空计算器,然后连续按下多个0,发现计算器的初始值没有任何变化。再清空计算器,按下2后输入多个0,值也是我们想要的结果。通过分析我们知道:

  • 当计算器的输入框中为0的时候,我们输入一个值是直接重新赋值
  • 当计算器的输入框中值不为0的时候,我们输入一个值是拼接的效果

实现代码如下:

for (let i = 0; i < allDiv.length; i++) {
  // 为所有按钮添加点击事件
    allDiv[i].onclick = function () {
    var text = this.innerText;

    //判断0~9之间的数
    if (text >= 0 && text < 10) {
      theDigital(text);
    }
  };
}

//如果是数字
function theDigital(number) {
  // 判断当前输入框中的数字是否为0
  // 如果为0直接重新赋值
  // 如果不为0拼接字符串
  if (box2.innerText === "0") {
    box2.innerText = number;
  } else {
    box2.innerText += number;
  }
}

2.小数点的处理

在上一小节中,我们已经能够正确的显示我们按下的数字了。在这一节中,花一点时间对小数点进行处理。
小数点的逻辑也很简单:

  • 一串数字中,只允许有一个小数点。
  • 如果数字中不存在小数点,则直接拼接上小数点即可
//判断0~9之间的数
if (text >= 0 && text < 10) {
	theDigital(text);
} else if (text == ".") {
	//判断如果按下的是小数点
	//判断当前的字符串中是否存在小数点,不存则拼接
	if (box2.innerText.indexOf(".") == -1) {
		box2.innerText += text;
	}
}
3.简单的运算

在前两节中,我们已经能在输入框中显示正确的数字。这一节,将要做一下简单的四则运算。
如何去进行四则运算呢,或者如何对数据进行存储呢?
当我们按下四则运算符的时候,就需要先对输入框中的数据进行存储,
假设我们规定了一个数组arr专门用来存储这些值,那么当我按下+的时候,我把输入框中的1存入到数组中,并且把+这个运算符也存入到数组中,这个时候数组中的值就是[1,"+"],接下来我们输入第二个数,第二个数为2,然后我又按了一个+这个时候就又需要把数据存到数组中,此时为[1,"+",2,"+"],最后按下0.2=我们就会得到这样一个数组格式:
var arr = [1, "+", 2, "+", 0.2, "="];
数组中第一个元素是数字,第二是运算符,第三个是数字,第四个是运算符,以此列推。。。
做计算的时候,我只需要: n和n+2之间通过n+1进行运算。如: 1 和 2 通过 + 进行运算结果就是1+2
看起来有点绕,但是这里必须要理解,因为后面的计算都将会用到这种格式的数组。接下来封装一个能计算这种数据的方法,如下:
index.js

//计算得数
function myCalc(arr) {
	//var arr = [1, "+", 2, "+", 0.2, "="]; arr的结构
	var sum = arr[0]; //总数

	//遍历数组
	for (var i = 1; i < arr.length - 1; i = i + 2) {
		//取出运算符及其后面一个元素进行运算
		var a = arr[i + 1];
		var symbol = arr[i]; //符号
		switch (symbol) {
			case "+":
				sum += a;
				break;
			case "-":
				sum -= a;
				break;
			case "x":
				sum *= a;
				break;
			case "÷":
				sum /= a;
				break;
		}
	}
	return sum; //返回结果
}

这个方法的作用就是来解析这种格式的数组的。
现在假设传参就是:[1, "+", 2, "+", 0.2, "="]我们来看一下它是如何计算的。
第4行代码把数组中第一个元素赋值给了sum这是sum值为1;
接下来是从下标1遍历到数组长度减1,就是[1,5)不包含5
当i为1时,第9行代码把arr[i+1]赋值给了a,也就是2
第10行代码又把arr[i]赋值给了symbol,也就是+
第11行代码进行判断,因为symbol是+,所以执行第13行代码sum += a,也就是1+=2,sum为3
完成一轮循环后i+2,i为3
当i为3时,第9行代码把arr[i+1]赋值给了a,也就是0.2
第10行代码又把arr[i]赋值给了symbol,也就是+
第11行代码进行判断,因为symbol是+,所以执行第13行代码sum += a,也就是3+=0.2,sum为3.2
完成这一轮循环后i+2,i为5不符合循环的条件,跳出循环,返回结果sum值为3.2;

现在我们已经能对上述格式的数组的进行计算了,接下来继续考虑做简单的四则运算,先分析下面的这个动图:

我们可以看到,先输入26然后×9,请注意输入框中的值当我们输入×的时候,上方的输入框的值变成了26 ×,下方的输入框中的值仍然为26,然后输入一个9时,上方输入框的值不变,下方输入框中的值为9,最后输入等于,上方的输入框值为26 * 9 =,下方为234,用代码实现这种操作呢?

  • 定义一个全局变量arr,用来保存数据
  • 将arr数组用空格拼接展示到上方的输入框
  • 按下等于计算结果

index.js

//定义全局变量,用来保存数据,包括运算符
var arr = [];
//判断是否进行了四则运算
var ifArithmetic = false;

for (let i = 0; i < allDiv.length; i++) {
	// 为所有按钮添加点击事件
	allDiv[i].onclick = function () {
		var text = this.innerText;
		//判断0~9之间的数
		if (text >= 0 && text < 10) {
			...
		} else if (text == ".") {}
			...
		} else if (text == "÷" || text == "x" || text == "-" || text == "+") {
			arithmetic(text);
		} else if(text == "="){
			ifEqual();
		}
	};
}

//如果是数字
function theDigital(number) {
  if (box2.innerText === "0" || ifArithmetic) {//这里发生了更改
    box2.innerText = number;
    ifArithmetic = false;
  } else {
    box2.innerText += number;
  }
}

//计算得数
function calc(arr) {...}

//四则运算
function arithmetic(text) {
  // 把输入框中的数和当前运算符存入到数组中
  arr.push(Number(box2.innerText), text);
  //对box1进行赋值
  box1.innerText = arr.join(" ");
  ifArithmetic = true;
}

//如果是等于
function ifEqual() {
  // 把输入框中的数和当前运算符存入到数组中
  arr.push(Number(box2.innerText), "=");
  //对box1进行赋值
  box1.innerText = arr.join(" ");
  //对box2进行赋值,值为运算后的结果
  box2.innerText = myCalc(arr);
}

push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。
join() 方法用于把数组中的所有元素转换一个字符串。元素是通过指定的分隔符进行分隔的。

上述代码中,我们判断了如果按下的是四则运算符,则执行arithmetic这个函数,函数中,我们先给arr数组存值(因为值为字符串,所以要先使用Number()方法转为数字),存入下方输入框的值,和运算符,然后在41行利用数组的join方法拼接数组成字符串。并赋值到box1的innerText属性中,这是为了能展示上方输入框的值。接着,我们把全局变量ifArithmetic设置为true,代表这里进行了一次四则运算。因为当我们按下下一个数字时,会重新赋值,而之前对数字的处理是,输入框为0时重新赋值,不为0时拼接,这里就需要重新判断了,如25行,当ifArithmetic为true时也需要重新赋值,并把ifArithmetic改为false,接下来就是判断如果按了等于以后的处理。我们在第17对=也进行了判断,执行ifEqual这个函数,这个函数内的前两句与四则运算一致,不过多解释,box2.innerText = myCalc(arr);则是计算数组中的值,并把结果显示在box2中,效果如下:

4.四则运算的处理

通过上一节的分析,已经能够实现基本的运算了,但是我们还有很多细节没有处理到,看下面动图:

我们发现,当按下四则运算符的时候,下方的输入框会自动计算值出来,当连续切换运算符的时候,上方的输入框的值也在变换。
下面我们就这两个问题进行解决,
1.按下运算符,下方输入框计算值。
这个简单我们只需要在arithmetic方法中添加box2.innerText = myCalc(arr);即可,
2.连续切换运算符时,上方的值进行变化。

//四则运算
function arithmetic(text) {
	//如果前一步进行了四则运算
	if (ifArithmetic) {
		//改变box1里面的最后一个四则运算的值
		arr[arr.length - 1] = text;
		box1.innerText = arr.join(" ");
		return;
	}
	...
}

我们需要在arithmetic中判断,ifArithmetic是否为true,如果是,则就是连续进行四则运算,就需要对改变数组中最后一个值,也就是最后一次的运算符。

现在四则运算基本上没有问题了,接下来处理小数点。
在之前做四则运算符的时候没有考虑到小数点的情况,导致现在如果运算后点击小数点是拼接的情况,如果你去windows的计算器中尝试你会发现,当你点击一次四则运算时,下次输入小数点的时候,输入框中的值是0.,你也可以现在去尝试一下。
下面是这个问题的解决代码

else if (text == ".") {
//前面按了四则
	if (ifArithmetic) {
		box2.innerText = "0" + text;
		ifArithmetic = false;
		return;
	}
	...
}

我们只需要在判断是小数点的时候,判断一下ifArithmetic,如果为true,则赋值成0.,并把ifArithmetic改为false即可。

5.’='的细节处理

我们对四则运算和小数点完成处理后,接下来,就要对等号进行处理了。请看下图:

这个图中放映了如下三种情况:

  • 按下等于后,按数字0~9先初始化再赋值
  • 按下等于后,按小数点也是先初始化再赋值
  • 连续按下等于,连续做相同运算

第一步,先定义一个全局变量ifDy用来判断,是否按了等于,并在ifEqual方法中给它赋值为true

//如果是等于
function ifEqual() {
	...
	ifDy = true;
}

接着定义一个init方法,用来初始化全局数据

//初始化数据
function init() {
	//初始化
	arr = [];
	ifArithmetic = false;
	ifDy = false;
	box1.innerText = "";
	box2.innerText = "0";
}

然后更改第一种情况,按下等于后,按数字0~9先初始化再赋值

//如果是数字
function theDigital(number) {
	//如果上一次按了等于
	if (ifDy) {
		init();
	}
	if (box2.innerText === "0" || ifArithmetic) {
		box2.innerText = number;
		ifArithmetic = false;
	} else {
		box2.innerText += number;
	}
}

上述的代码,这是在判断ifDy为true的时候调用了init方法。
第二种情况也是同样的结果方法,

else if (text == ".") {
	if (ifDy) {
		init();
	}
	//前面按了四则
	if (ifArithmetic) {
		box2.innerText = "0" + text;
		ifArithmetic = false;
		return;
	}
	...
}

我们最主要的是分析第三种情况,连续点击,
先对案例进行分析,
当输入22+6-3+9然后按下等于的时候,结果如左边一张图,再按一次等于后结果为右边这张图,两个图的区别在哪里?
首先34和43之间的关系是34+9,9是从左边一张图上面一个输入框中得到的也就是arr[arr.length - 2],+则是arr[arr.length - 3],34是左边一张图下面输入的值,现在34,+,9这三个值我们都能得到,在拿到一个等于就可以拼接成右边一张图了,代码如下:

//如果是等于
function ifEqual() {
	if (ifDy) {
		//把上一次的计算结果写到box1里面,加上数组的后三位数
		var arr2 = [];
		arr2.push(
			Number(box2.innerText),
			arr[arr.length - 3],
			arr[arr.length - 2],
			arr[arr.length - 1]
		);
		box1.innerText = arr2.join(" ");
		box2.innerText = myCalc(arr2);
		return;
	}
	...
}

上述代码把4个值push到了arr2中,并拼接成字符串显示到了上方输入框,下方输入框显示计算后的值。

现在以上三种情况都解决了,但是还有一种情况我在上面的动图中并没有展现出来。就是按了=之后再按四则运算符,这里就不过多描述了,读者可以自己试下,现在上代码。

//四则运算
function arithmetic(text) {
	//如果上一步是等于
	if (ifDy) {
		arr = [];
		ifDy = false;
	}
	...
}

处理方式也很简单,只需要把arrifDy初始化即可。

6.剩余功能和优化

到目前为止计算器的功能基本实现了,还有两个功能也非常的简单。

else if (text == "C") {
	init();
} else if (text == "CE") {
	//如果是CE清除box2中的所有内容
	box2.innerText = 0;
	if (ifDy) {
		init();
	}
}

如果是C直接初始化,如果是CE情况下方输入框的值,要是上一次按了=则同样清空。

浮点数计算的处理!!!
关于浮点数的处理,可以直接去看这个博客JS浮点数计算误差解释及解决方案
这里直接引用解决方案

 //浮点数加
 function Add(arg1, arg2) {
   var r1, r2, m;
   try {
     r1 = arg1.toString().split(".")[1].length
   } catch (e) {
     r1 = 0
   }
   try {
     r2 = arg2.toString().split(".")[1].length
   } catch (e) {
     r2 = 0
   }
   m = Math.pow(10, Math.max(r1, r2));
   return (arg1 * m + arg2 * m) / m;
 }
 //浮点数减
 function Sub(arg1, arg2) {
   var r1, r2, m, n;
   try {
     r1 = arg1.toString().split(".")[1].length
   } catch (e) {
     r1 = 0
   }
   try {
     r2 = arg2.toString().split(".")[1].length
   } catch (e) {
     r2 = 0
   }
   m = Math.pow(10, Math.max(r1, r2));
   //动态控制精度长度
   n = (r1 >= r2) ? r1 : r2;
   return ((arg1 * m - arg2 * m) / m).toFixed(n);
 }
 //浮点数乘
 function Mul(arg1, arg2) {
   var m = 0,
     s1 = arg1.toString(),
     s2 = arg2.toString();
   try {
     m += s1.split(".")[1].length
   } catch (e) {}
   try {
     m += s2.split(".")[1].length
   } catch (e) {}
   return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
 }
 //浮点数除
 function Div(arg1, arg2) {
   var t1 = 0,
     t2 = 0,
     r1, r2;
   try {
     t1 = arg1.toString().split(".")[1].length
   } catch (e) {}
   try {
     t2 = arg2.toString().split(".")[1].length
   } catch (e) {}
   r1 = Number(arg1.toString().replace(".", ""));
   r2 = Number(arg2.toString().replace(".", ""));
   return (r1 / r2) * Math.pow(10, t2 - t1);
 }

然后改造myCalc方法

//计算得数
function myCalc(arr) {
  var sum = arr[0]; //总数

  //遍历数组
  for (var i = 1; i < arr.length - 1; i = i + 2) {
    //取出运算符及其后面一个元素进行运算
    var a = arr[i + 1];
    var symbol = arr[i]; //符号
    switch (symbol) {
      case "+":
        sum = Add(sum, a);
        break;
      case "-":
        sum = Sub(sum, a);
        break;
      case "x":
        sum = Mul(sum, a);
        break;
      case "÷":
        sum = Div(sum, a);
        break;
    }
  }
  return sum; //返回结果
}

总结

本片文章注意介绍了简易计算器的实现过程,代码都很简单,需要注意的就是分析,在这个计算器中还有很多功能和细节没有去处理,比如,除数不能为0。这个需要大家自己去完善。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值