定时器为什么不准
setTimeout 和 setInterval 的差异
两个方法都是用于执行定时任务的函数,主要差异存在于执行次数和间隔的处理方式。
setTimeout
在一定延迟后执行一次指定的函数或代码块。执行完成后停止计时。
setInterval
每隔一定时间重复执行指定函数,会不断执行直到被取消或者页面销毁。
定时器的一些注意事项:
-
js 中定时器不能准时执行,主要原因包括以下几点:
- JavaScript 是单线程执行的语言,在同一时间只执行一个任务。在执行 JavaScript 代码时,如果遇到长时间运行的任务(大量计算或阻塞操作),会导致计时器执行收到影响。
- 浏览器有最小定时器间隔,通常为 4ms 或 10ms。浏览器总会将时间精度舍入到最小间隔的倍数。
- 页面活动状态也会影响计时器,在页面切换标签或者最小化浏览器窗口时,浏览器可能会将当前页面的定时器暂停,以节省资源。
- 在移动设备或者资源受限的环境中,系统可能会对浏览器资源进行限制。
- 延迟执行时间有最大值 2147483647ms。
-
this
指向问题:如果回调函数是某个对象的方法,this
会指向全局,而不是对应的对象。const name = 'window'; const obj = { name: 'obj', say: function () { console.log(this.name); } } setTimeout(obj.say, 10); // log: window;
可以通过 箭头函数、call/bind/apply、function 函数 解决
this
指向问题。
浏览器实现定时器的方式:
浏览器是通过消息队列去维护任务,要执行一段异步任务,需要先将任务添加到消息队列中。不过通过定时器设置的回调函数比较特殊,需要在指定的时间间隔被调用,而消息队列中的任务是按照顺序执行的,为了保证回调函数在指定的时间内执行,不能将定时器任务添加到消息队列。
- Chrome 除了正常使用的消息队列之外,还有一个消息队列(延迟队列),在这个队列中维护了需要延迟执行的任务列表。当创建了定时器时,渲染进程会将定时器的回调任务添加到这个队列中。
- Chrome 中有一个 ProcessDelayTask 函数,专门用来处理延迟任务。执行时机是在处理完消息队列中的一个任务之后。
- 取消定时器(clearTimeout)实际就是从延迟队列中找出编号对应的任务,将其从队列中删除。
替代 setTimeout 的方式:
对于时间精度要求高的动画效果来说, requestAnimationFrame 函数是一个好选择。
- 该函数提供一个原生的 API 执行动画效果,在一帧间隔内根据浏览器情况执行相关动作。
- 回调函数是在页面刷新之前执行,跟着屏幕的刷新频率走,保证每个刷新间隔只执行一次。
- 如果页面未激活,也会停止渲染,保证了页面流畅性的同时节省主线程执行函数的开销。
节流和防抖
节流(Throttling)和防抖(Debouncing)是两种常见的性能优化技术,用于限制函数的执行频率。在 JavaScript 中一般通过定时器和闭包实现。
- 节流是指在一定时间间隔内只执行一次函数。这可以防止函数被频繁调用,适用于诸如滚动事件和鼠标移动事件等高频触发的场景。
function throttle(func, delay) {
let lastExecTime = 0;
return function () {
const context = this;
const args = arguments;
const currentTime = new Date();
if (currentTime - lastExecTime > delay) {
func.apply(context, args);
lastExecTime = currentTime;
}
};
}
- 防抖是指在事件触发后等待一段时间,只有在该时间段内没有再次触发该事件时才执行函数。这可以防止函数被连续触发多次,适用于诸如搜索输入框输入事件等需要等待用户停止操作后执行的场景。
function debounce(func, delay) {
let timeout;
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(context, args);
}, delay);
};
}