從無到有實現一個網頁計算器


一、需求:

大概的需求就是制作一個簡易版的仿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函數得出計算結果。

完整的項目代碼在這里:https://github.com/popelnice/calculator

最后,如果各位有更好的實現方式,麻煩在評論中告知在下,不勝感激!!!大家共同進步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM