一, 需求:
大概的需求就是制作一个简易版的仿 OS X 系统的计算器, 主要功能点有:
重置(AC).
+-*/ 运算.
数字和小数点的输入.
输入左右运算数及运算符之后点击等号可以进行重复计算.
二, 界面设计:
先来看看最终的实现效果:
三, 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 类文件, 类之间通过事件通知机制进行通信.
Monitor.JS 显示器类, 监听显示事件并将内容进行展示.
Number.JS 数字输入类, 监听数字按键的点击并进行广播.
Operator.JS 运算符输入类, 监听运算符的点击并进行广播.
Calculator.JS 计算器类, 监听, 广播事件并进行结果的计算.
除此之外, 还需要一个 EventEmitter.JS 自定义事件对象, 来完成自定义事件的监听和触发.
程序大致的运行流程如下:
点击数字按键, Number 广播一个数字按键事件.
Calculator 监听数字按键事件, 并将输入数字作为左侧运算数.
点击运算符, Operator 广播一个运算符按键事件.
Calculator 监听运算符按键事件, 并将输入作为运算符.
点击等号, Operator 广播一个运算符按键事件.
Calculator 监听运算符按键事件, 并计算结果, 广播一个显示内容事件.
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
最后, 如果各位有更好的实现方式, 麻烦在评论中告知在下, 不胜感激!!! 大家共同进步.
来源: https://www.cnblogs.com/popelnice/p/10024986.html