三亩地 三亩地SAN MU DI · CODE DIARY
CASE STUDY · 005

简易计算器

用 JavaScript 写一个能连续运算的计算器。表面是按钮 + 显示屏,里子是三个状态之间的小型状态机。

简易计算器界面预览
前端 · 入门

看起来最简单,藏着状态管理的第一课

「12 + 5 = 17,再按 × 3」这个连算怎么处理?按 5 之前要不要先清屏?连续按两次加号会不会出错?这些细节逼着你开始用「状态」思考:屏幕显示什么、当前在等什么、按下去的按钮是数字还是运算符。这是第一次接触状态机的雏形,比写之前所有的项目都更训练逻辑。

技术栈 JavaScript / 状态管理
预计耗时 3 - 5 小时
难度等级 入门
核心难点 三状态流转
事件委托 data-* 属性 状态机 parseFloat Grid 布局
STEPS

四步做一个不傻的计算器

难点全在「连续按运算符」与「按等号之后继续算」这两个边界,其他都是表面功夫。

1

画界面:显示屏 + 按钮网格

用一个 div 当显示屏,下面用 display: grid; grid-template-columns: repeat(4, 1fr); 排 16 个按钮:0-9、加减乘除、等号、清除、小数点。每个按钮加 data-action="num|op|eq|clear|dot"data-value 属性,方便后面用事件委托统一处理。

2

定义三个状态变量

核心:current(当前显示屏上的字符串)、previous(上一次输入的数)、operator(当前运算符,null 表示还没选)。所有按钮的点击都只是在「改这三个变量 + 重新渲染 current」。

3

处理运算符按钮

按运算符时:如果 previous 为空,把 current 存进 previous,清空 current;如果 previous 已有值且 current 也非空,先算出 previous operator current 的结果,存进 previous,再记下新运算符。这就是「连算」的关键——每次按运算符都先把前面的算掉。

4

等号、清除与小数点

等号:算出 previous operator current,结果显示出来后把 previous 清空(这样再按数字会开始新算式)。清除:三个状态全置空。小数点:如果 current 里已经有 . 就忽略,否则追加。处理完这四个边界,计算器就比大多数人手机自带的还耐造了。

CODE

关键代码片段

四个状态变量 + 一个事件委托,足以撑起一个完整计算器。

let current = '', previous = null, operator = null;
const screen = document.querySelector('.screen');

function compute(a, op, b) {
  switch (op) {
    case '+': return a + b;
    case '-': return a - b;
    case '×': return a * b;
    case '÷': return b === 0 ? 'Error' : a / b;
  }
}

document.querySelector('.pad').addEventListener('click', e => {
  const btn = e.target.closest('button');
  if (!btn) return;
  const { action, value } = btn.dataset;

  if (action === 'num') {
    current = current === '0' ? value : current + value;
  } else if (action === 'dot') {
    if (!current.includes('.')) current = (current || '0') + '.';
  } else if (action === 'op') {
    if (previous !== null && current) {
      previous = compute(previous, operator, parseFloat(current));
    } else {
      previous = parseFloat(current);
    }
    operator = value; current = '';
  } else if (action === 'eq') {
    if (previous !== null && operator && current) {
      current = String(compute(previous, operator, parseFloat(current)));
      previous = null; operator = null;
    }
  } else if (action === 'clear') {
    current = ''; previous = null; operator = null;
  }
  screen.textContent = current || '0';
});

计算器教给我一件事:越是看起来「就那点功能」的项目,越要在动手前把状态画清楚。三个变量 + 一个事件函数,全靠「按下每个按钮后这三个变量分别变成什么」这张表撑着。后来写 Redux、写 React reducer,本质都是同一张状态转移表,只是粒度更细。

—— 三亩地 · 案例笔记
NEXT

继续下一个案例

状态管理练完,下一步进入现代浏览器 API,用 IntersectionObserver 做图片懒加载。

← 上一篇:Markdown 编辑器 下一篇:图片懒加载组件 →
或者去小型实战项目继续练 →