必须用 setinterval 而非 settimeout 递归,因其能固定节奏调度、避免延迟累积和后台节流导致的“狂转”;new date() 必须在回调内实时调用,不可缓存累加;时间字段均从该实例获取,补零与角度换算需基于实时 now 计算。

setInterval 是必须的,但只写 setInterval("func()", 1000) 容易出错;Date 对象每次都要重新实例化,不能缓存或累加;补零、角度换算、DOM 更新顺序这些细节一错,时钟就卡顿、跳秒或显示异常。
为什么用 setInterval 而不是 setTimeout 递归?
用 setTimeout 在回调里再调自己,看似一样,实际风险很高:
– 页面切到后台时,浏览器会节流甚至暂停定时器,切回来可能批量触发,秒针“狂转”
– 执行延迟会累积,比如某次更新花了 120ms,下次就从 1120ms 后开始,越拖越偏
– setInterval 是固定节奏调度,只要函数内用 new Date() 实时取值,就能对齐系统时间
– 别传动态毫秒数(比如 setInterval(fn, 1000 - (Date.now() % 1000))),没意义还增加复杂度
new Date() 必须在定时器回调里调用
常见错误是把 new Date() 提到外面,当成“初始时间”然后靠变量累加:
– 错误写法:let base = new Date(); setInterval(() => { base.setSeconds(base.getSeconds() + 1); ... }, 1000)
– 这样完全脱离系统时钟,几分钟后就会明显偏差,且无法响应系统时间修改(如手动校准)
– 正确做法:每次进 setInterval 回调第一件事就是 const now = new Date();
– 所有时间字段(getHours()、getMinutes()、getSeconds())都从这个 now 取,不复用、不缓存
数字时钟补零和模拟时钟角度怎么算才准?
补零别手写 hours ,直接用 <code>String(hours).padStart(2, '0') 更稳;模拟时钟角度要区分“整点跳动”和“平滑过渡”:
– 秒针角度:(seconds / 60) * 360(严格每秒 6°)
– 分针角度:((minutes + seconds / 60) / 60) * 360(带秒级微调,避免“咔哒”感)
– 时针角度:(((hours % 12) + minutes / 60) / 12) * 360(每分钟推进 0.5°)
– CSS 中指针元素必须设 transform-origin: bottom center,否则旋转中心偏移
DOM 更新别在循环里反复查元素
如果同时更新数字显示和三根指针,不要每次都在回调里写三次 document.getElementById:
– 初始化时就缓存好:const clockEl = document.getElementById('clock');、const secHand = document.querySelector('.second');
– 更新时只改 .innerHTML 或 .style.transform,不重复 DOM 查询
– 更进一步:把三个角度先算好,再统一赋给对应元素的 style.transform,避免浏览器重排重绘抖动
– 如果用 requestAnimationFrame 做秒针平滑,必须配合 CSS transition: transform 0.1s linear,且该样式要写在默认 class 里,不能 JS 动态加
真实项目里最容易被忽略的,是页面加载完成前就执行定时器,或者没处理 DOMContentLoaded 就操作 DOM —— 这会导致 getElementById 返回 null,整个时钟静默失效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











