将播放/暂停双按钮合并为单个切换按钮,实现状态感知的计时器控制

本文详解如何将独立的“开始”和“暂停”按钮重构为一个智能切换按钮,通过布尔标志管理计时器运行状态,并动态切换 font awesome 图标(fa-play ↔ fa-pause),同时修复原始代码中 `var` 全局污染、递归 `settimeout` 风险及 dom 更新逻辑缺陷。

要实现「单按钮切换播放/暂停」功能,核心在于两点:状态记忆图标响应式更新。原始代码使用两个独立按钮分别绑定 startTimer() 和 clearTimeout(),缺乏对当前计时器状态的判断,导致无法统一控制;同时采用 var 声明全局变量、递归调用 startTimer()(易引发堆栈溢出)、且未正确处理 hours/mins 进位时的秒数重置逻辑。

以下是完整、健壮的重构方案:

✅ 步骤一:HTML 结构精简

仅保留一个按钮,并预设初始图标为播放态:




00:
00:
00

✅ 步骤二:JavaScript 逻辑重构

let hours = 0, mins = 0, seconds = 0;
let timerId = null;           // 存储 setTimeout 返回的 ID,用于 clearTimeout
let isRunning = false;        // 核心状态标志:true=运行中,false=已暂停/未启动

const btn = document.getElementById('startStop');

btn.addEventListener('click', function() {
  if (isRunning) {
    // 暂停:清除定时器,更新状态与图标
    clearTimeout(timerId);
    isRunning = false;
    btn.querySelector('i').classList.replace('fa-pause', 'fa-play');
  } else {
    // 启动:调用计时器函数,更新状态与图标
    startTimer();
    isRunning = true;
    btn.querySelector('i').classList.replace('fa-play', 'fa-pause');
  }
});

function startTimer() {
  timerId = setTimeout(() => {
    seconds++;

    // 秒进位处理
    if (seconds >

59) { seconds = 0; mins++; } // 分进位处理 if (mins > 59) { mins = 0; hours++; } // DOM 更新:统一格式化(补零) $('#hours').text(hours < 10 ? `0${hours}:` : `${hours}:`); $('#mins').text(mins < 10 ? `0${mins}:` : `${mins}:`); $('#seconds').text(seconds < 10 ? `0${seconds}` : `${seconds}`); // 递归调用下一轮(注意:实际生产环境建议改用 setInterval 更安全) startTimer(); }, 1000); }

⚠️ 关键注意事项

  • 避免 var 全局污染:全部改用 let,确保变量作用域可控;
  • timerId 必须可清除:每次 setTimeout 返回新 ID,必须赋值给同一变量,否则 clearTimeout 无效;
  • 图标切换原理:利用 Element.classList.replace(oldClass, newClass) 精准替换 Font Awesome 类名,无需操作 innerHTML;
  • setInterval 更推荐:当前示例仍用递归 setTimeout 以兼容原逻辑,但长期运行建议改用 setInterval + clearInterval 组合,更易维护且无调用栈风险;
  • 边界健壮性:代码已修正原始逻辑中 mins > 59 判断嵌套过深导致 seconds++ 后未及时更新显示的问题。

✅ 最终效果

点击按钮 → 启动计时器,图标变为 ▶️(fa-pause);
再次点击 → 暂停计时器,图标切回 ⏸️(fa-play);
状态完全由 isRunning 标志驱动,逻辑清晰、无冗余 DOM 查询。

通过这一改造,不仅提升了 UI 简洁性,更强化了代码的可读性、可维护性与健壮性——这才是现代前端交互开发的标准实践。