欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

从无到有实现一个网页计算器

程序员文章站 2022-07-05 14:38:52
一、需求: 大概的需求就是制作一个简易版的仿OSX系统的计算器,主要功能点有: 二、界面设计: 先来看看最终的实现效果: 三、HTML结构设计: 页面结构主要分为输入及计算结果展示区域(monitor)和键盘区域(keyboard)。 四:样式设计: css代码如下: 五、架构设计: 根据单一职责原 ......

一、需求:

大概的需求就是制作一个简易版的仿osx系统的计算器,主要功能点有:

  1. 重置(ac)。
  2. +-*/运算。
  3. 数字和小数点的输入。
  4. 输入左右运算数及运算符之后点击等号可以进行重复计算。

二、界面设计:

先来看看最终的实现效果:

从无到有实现一个网页计算器

三、html结构设计:

页面结构主要分为输入及计算结果展示区域(monitor)和键盘区域(keyboard)。

<div id="calculator" class="calculator-panel">
  <div class="monitor">
    <div class="display"></div>
  </div>
  <div class="keyboard">
    <div class="left">
      <div class="top">
	   <ul>
	     <li class="operator largest">ac</li>
	   </ul>
	   </div>
	   <div class="bottom">
	     <ul>
	       <li class="number">7</li>
	       <li class="number">8</li>
		  <li class="number">9</li>
		  <li class="number">4</li>
		  <li class="number">5</li>
		  <li class="number">6</li>
		  <li class="number">1</li>
		  <li class="number">2</li>
		  <li class="number">3</li>
		  <li class="number large">0</li>
		  <li class="number">.</li>
	     </ul>
       </div>
    </div>
    <div class="right">
      <ul>
        <li class="operator">÷</li>
	   <li class="operator">×</li>
	   <li class="operator">-</li>
        <li class="operator">+</li>
	   <li class="operator">=</li>
      </ul>
    </div>
  </div>
</div>

 四:样式设计:

css代码如下:

* {
	box-sizing: border-box;
}
.calculator-panel {
	width: 200px;
	height: 350px;
	color: #fff;
	font-size: 18px;
	background: rgba(100, 98, 96);
}
.calculator-panel ul {
	padding: 0;
	margin: 0;
	list-style: none;
}
.calculator-panel li {
	float: left;
	width: 50px;
	height: 50px;
	text-align: center;
	line-height: 50px;
	border: 1px solid rgba(72, 74, 65);
}
.monitor {
	height: 100px;
	padding: 60px 10px 0 10px;
	font-size: 36px;
}
.monitor > .display {
	height: 40px;
	line-height: 40px;
	text-align: right;
}
.keyboard {
	height: 250px;
}
.keyboard > .left,
.keyboard > .right {
	float: left;
	height: 100%;
}
.keyboard > .left {
	width: 150px;
}
.keyboard > .left .operator {
	background: rgba(81, 80, 80);
}
.keyboard > .left .operator.largest {
	width: 150px;
}
.keyboard > .left .number {
	background: rgba(108, 108, 108);
}
.keyboard > .left .number.large {
	width: 100px;
}
.keyboard > .right {
	width: 50px;
}
.keyboard > .right .operator {
	background: rgba(232, 157, 41);
}

五、架构设计:

根据单一职责原则,将功能拆分为4个js类文件,类之间通过事件通知机制进行通信。

  1. monitor.js显示器类,监听显示事件并将内容进行展示。
  2. number.js数字输入类,监听数字按键的点击并进行广播。
  3. operator.js运算符输入类,监听运算符的点击并进行广播。
  4. calculator.js计算器类,监听、广播事件并进行结果的计算。

除此之外,还需要一个eventemitter.js自定义事件对象,来完成自定义事件的监听和触发。

程序大致的运行流程如下:

  1. 点击数字按键,number广播一个数字按键事件。
  2. calculator监听数字按键事件,并将输入数字作为左侧运算数。
  3. 点击运算符,operator广播一个运算符按键事件。
  4. calculator监听运算符按键事件,并将输入作为运算符。
  5. 点击等号,operator广播一个运算符按键事件。
  6. calculator监听运算符按键事件,并计算结果,广播一个显示内容事件。
  7. monitor监听显示内容事件,并将结果进行显示。

六、代码展示:

eventemitter.js

// 自定义事件监听/触发器
var eventemitter = {
	eventloops: {},									// 事件队列
	subscribe: function (eventname, handler) {					// 订阅事件
		var handlers = this.eventloops[eventname];
		if (!array.isarray(handlers)) {
			handlers = this.eventloops[eventname] = [];
		}
		handlers.push(handler);
	},
	emit: function (eventname) {							// 触发事件
		var args = [].slice.call(arguments, 1);
		var handlers = this.eventloops[eventname];
		handlers.foreach(function (handler) {
			handler(...args);
		});
	},
	remove: function (eventname) {						     // 移除事件
		delete this.eventloops[eventname];
	}
};

monitor.js

// 监视器构造函数
var monitor = function monitor () {
	this.node = $(".monitor").find(".display");
};

// 初始化
monitor.prototype.init = function () {
	this.subscribe();
};

// 订阅事件并进行内容显示
monitor.prototype.subscribe = function () {
	eventemitter.subscribe("calculator.show", (content) => { this.node.text(content); });
};

// 销毁
monitor.prototype.destroy = function () {
	this.node = null;
	eventemitter.remove("calculator.show");
};

number.js

// number输入类构造函数
var number = function number () {	
	// 当前输入累加值
	this.value = "0";
	// dom元素class前缀
	this.prefix = "number";
};

// 初始化
number.prototype.init = function () {
	this.subscribe();
	// 先执行一次事件,将当前值进行显示
	eventemitter.emit("calculator.show", this.value);
};

// 订阅事件
number.prototype.subscribe = function () {
	var self = this;
	// 订阅number按钮的点击事件
	$("." + this.prefix).on("click", function (e) {
		var value = $(e.target).text();
		self.value = self.value === "0" ? value : self.value + value;
		eventemitter.emit("calculator.show", self.value);
		eventemitter.emit("calculator.number", self.value);
	});
	// 订阅value重置事件并重置value值
	eventemitter.subscribe("calculator.number.reset", () => { this.value = "0"; });
};

// 销毁
number.prototype.destroy = function () {
	$("." + this.prefix).off("click");
	eventemitter.remove("calculator.number.reset");
};

operator.js

// 运算符构造函数
var operator = function operator () {
	this.prefix = "operator";
};

// 初始化
operator.prototype.init = function () {
	this.subscribe();
};

// 订阅事件
operator.prototype.subscribe = function () {
	var self = this;
	// 订阅运算符点击事件
	$("." + this.prefix).on("click", function (e) {
		var value = $(e.target).text();
		eventemitter.emit("calculator.operator", value);
	});
};

// 销毁
operator.prototype.destroy = function () {
	$("." + this.prefix).off("click");
};

calculator.js

// 计算器构造函数,主入口
var calculator = function calculator () {
	// 左侧数值
	this.left = null;
	// 右侧数值
	this.right = null;
	// 运算符
	this.operator = null;
	// 当前输入模式,"left"表示当前输入的是左侧数值,"right"表示当前输入的右侧数值
	this.mode = "left";
	// 匹配等号
	this.equals = [ "=" ];
	// 特殊运算符
	this.specialoperators = [ "ac" ];
	// 匹配基本运算符
	this.basicoperators = [ "÷", "×", "-", "+" ];
	// 基本运算符映射
	this.basicoperatormappings = {
		"÷": "/",
		"×": "*",
		"-": "-",
		"+": "+"
	};
};

// 初始化
calculator.prototype.init = function () {
	this.monitorinstance = new monitor();
	this.numberinstance = new number();
	this.operatorinstance = new operator();
	
	this.monitorinstance.init();
	this.numberinstance.init();
	this.operatorinstance.init();
	
	this.subscribe();
};

// 取消订阅事件
calculator.prototype.unsubscribe = function () {
	eventemitter.remove("calculator.number");
	eventemitter.remove("calculator.operator");
};

// 订阅事件
calculator.prototype.subscribe = function () {
	eventemitter.subscribe("calculator.number", (number) => { this.onnumberinput(number); });
	eventemitter.subscribe("calculator.operator", (operator) => { this.onoperatorinput(operator); });
};

// 监听数值输入
calculator.prototype.onnumberinput = function (number) {
	// 当前输入的为左侧数值
	if (this.mode === "left") this.left = number;
	// 当前输入的为右侧数值
	if (this.mode === "right") this.right = number;
};

// 监听运算符输入
calculator.prototype.onoperatorinput = function (operator) {
	// 当前输入的是等号,[ "=" ]
	if (this.equals.includes(operator)) {
		// 排除不合法操作
		if (this.operator == null) return;
		if (this.left == null && this.right == null) return;
		if (this.left == null || this.right == null) return;
		this.calcresult();
	// 当前输入的基本运算符,[ "÷", "×", "-", "+" ]
	} else if (this.basicoperators.includes(operator)) {
		// 排除不合法操作
		if (this.left == null) return;
		
		// 获取真实操作运算符,防止[ "÷", "×" ]这类非法运算符参与计算
		this.operator = this.basicoperatormappings[operator];
		// 切换当前输入为右侧数字
		this.mode = "right";
		
		// 重置当前number的value,以便重新输入右侧数值
		eventemitter.emit("calculator.number.reset");
	// 特殊运算符[ "ac" ]
	} else if (this.specialoperators.includes(operator)) {
		this.reset();
	}
};

// 计算结果
calculator.prototype.calcresult = function () {
	// 根据左侧、右侧数值加上运算符计算出结果
	// 将结果作为左侧数值继续参与计算
	var result = this.left = eval(`${this.left}${this.operator}${this.right}`);
	
	// 切换当前输入为右侧数字
	this.mode = "right";
	// 重置当前number的value,以便重新输入右侧数值
	eventemitter.emit("calculator.number.reset");
	
	// 显示计算结果
	eventemitter.emit("calculator.show", result);
};

// 重置
calculator.prototype.reset = function () {
	this.monitorinstance.destroy();
	this.numberinstance.destroy();
	this.operatorinstance.destroy();
	this.unsubscribe();
	
	this.left = null;
	this.right = null;
	this.operator = null;
	this.mode = "left";
	
	this.init();
	eventemitter.emit("calculator.number.reset");
};

七、总结:

核心思路就是calculator中定义this.left、this.right、this.operator来存储左、右运算数以及当前运算符,点击等号"="时通过将拼接的运算表达式传入eval函数得出计算结果。

完整的项目代码在这里:

最后,如果各位有更好的实现方式,麻烦在评论中告知在下,不胜感激!!!大家共同进步。