javascript三种定时器原理和异同

168 阅读1分钟

前言

最近刚好遇到了计时器相关的需求,那么就浅研究下,不同定时器的原理和异同。众所周知js定时器有三种 setTimeout和setInterval 还有一种不太常用的requeTanimationFrame,

那么接下来我就简单的介绍一下,都是什么定时器,又要如何使用,在什么场景使用,以及三者的区别。



一、setTimeout定时器

1.1 定义

setTimeout是JavaScript中的一个定时器函数,用于在指定的时间后执行一次指定的函数或代码块。

1.2 语法

setTimeout(function, milliseconds, param1, param2, ...)

参数说明:

function:必需,要执行的函数或代码块。

milliseconds:必需,延迟的毫秒数。

param1, param2, ...:可选,传递给函数的参数。

1.3 使用场景

setTimeout适用于需要延迟执行某些操作的场景,比如:

1.延迟执行某个函数或代码块。

2.延迟执行某个动画效果。

3.延迟执行某个异步操作。

1.4 优缺点

优点:

1.简单易用,语法简单。

2.可以设置延迟时间,灵活性高。

缺点:

1.由于JavaScript是单线程执行的,如果setTimeout中的代码执行时间过长,会影响后续代码的执行。

2.如果设置的延迟时间不准确,可能会导致代码执行不稳定。

3.如果需要多次执行某个操作,需要多次调用setTimeout函数。

1.5 示例代码



// 延迟执行某个函数
setTimeout(function(){
    console.log('Hello World!');
}, 1000);
// 延迟执行某个动画效果
var box = document.getElementById('box');
setTimeout(function(){
    box.style.width = '200px';
    box.style.height = '200px';
}, 1000);
// 延迟执行某个异步操作
setTimeout(function(){
    $.ajax({
        url: 'http://www.example.com',
        success: function(data){
            console.log(data);
        }
    });
}, 1000);

二、setInterval定时器

2.1 定义

setInterval是JavaScript中的一个定时器函数,用于每隔一定时间执行一次指定的函数或代码块。

2.2 语法

setInterval(function, milliseconds, param1, param2, ...)

参数说明:

function:必需,要执行的函数或代码块。

milliseconds:必需,每隔多少毫秒执行一次。

param1, param2, ...:可选,传递给函数的参数。

2.3 使用场景

setInterval适用于需要每隔一定时间执行某些操作的场景,比如:

1.定时更新某个数据。

2.定时执行某个动画效果。

3.定时检查某个状态。

2.4 优缺点

优点:

1.可以每隔一定时间执行某个操作,方便实现定时任务。

2.可以设置间隔时间,灵活性高。

缺点:

1.由于JavaScript是单线程执行的,如果setInterval中的代码执行时间过长,会影响后续代码的执行。

2.如果设置的间隔时间不准确,可能会导致代码执行不稳定。

3.如果需要停止定时器,需要手动调用clearInterval函数。

2.5 示例代码



// 每隔1秒钟输出一次Hello World!
setInterval(function(){
    console.log('Hello World!');
}, 1000);
// 每隔1秒钟执行一次动画效果
var box = document.getElementById('box');
var width = 100;
setInterval(function(){
    width += 10;
    box.style.width = width + 'px';
}, 1000);
// 每隔1秒钟检查一次某个状态
setInterval(function(){
    if (document.readyState === 'complete') {
        console.log('页面加载完成!');
    }
}, 1000);

三、requestAnimationFrame定时器

3.1 定义

requestAnimationFrame是JavaScript中的一个定时器函数,用于在下一次浏览器重绘之前执行指定的函数或代码块。

3.2 语法

requestAnimationFrame(callback)

参数说明:

callback:必需,要执行的函数或代码块。

3.3 使用场景

requestAnimationFrame适用于需要在下一次浏览器重绘之前执行某些操作的场景,比如:

1.实现动画效果。

2.实现游戏循环。

3.实现页面滚动效果。

3.4 优缺点

优点:

1.由于requestAnimationFrame是在下一次浏览器重绘之前执行,可以避免由于代码执行时间过长导致的卡顿现象。

2.可以实现更加流畅的动画效果。

缺点:

1.不支持IE9及以下版本浏览器。

2.由于requestAnimationFrame是在下一次浏览器重绘之前执行,如果需要立即执行某个操作,需要手动调用setTimeout或setInterval函数。

3.5 示例代码



// 实现动画效果
var box = document.getElementById('box');
var width = 100;
function animate() {
    width += 10;
    box.style.width = width + 'px';
    if (width < 200) {
        requestAnimationFrame(animate);
    }
}
requestAnimationFrame(animate);
// 实现游戏循环
function gameLoop() {
    // 更新游戏状态
    // 绘制游戏画面
    requestAnimationFrame(gameLoop);
}
requestAnimationFrame(gameLoop);
// 实现页面滚动效果
var scrollTop = 0;
function scroll() {
    scrollTop += 10;
    window.scrollTo(0, scrollTop);
    if (scrollTop < 1000) {
        requestAnimationFrame(scroll);
    }
}
requestAnimationFrame(scroll);



四、个人测试结果 1、使用requestAnimationFrame和时间戳去模拟setInterval的方案,误差大概是一帧时间(16ms)

2、使用setTimout和时间戳的方案,也存在误差; 而且setState异步,react更新也会有耗时,还是不能做到精准秒级更新时间;

3、也看了下web worker的方案,在Can I use看着兼容性一般,不适合前段同学使用;

综合分析下来,在我所需要的计时器场景中,还是原生的setInterval是使用最为广泛的,误差也在可接受范围内的方案,测试的时候我自己mock的情况开了个页面空置几个小时,看起来没有异常,只需要保证单独模块内只有一个计时器就不会出错,并且要合理使用clearInterval();

五、写在最后

最后,这就是我的计时器最终采取的方案啦~

 useEffect(() => {
     const interval = setInterval(() => {
         setCurrentTime(currentTime => currentTime + 1000)
     }, 1000);
     return () => clearInterval(interval);
 }, [startTime]);

 const formatTime = (time: number): string => {
     if (time < 60) {
         return `${time}秒`;
     } else if (time < 3600) {
         const minutes = Math.floor(time / 60);
         const seconds = time % 60;
         if(seconds === 0) {
             return `${minutes}分`
         }
         return `${minutes}${seconds}秒`;
     } else if (time < 86400) {
         const hours = Math.floor(time / 3600);
         return `${hours}小时`;
     } else if (time < 864000) {
         const days = Math.floor(time / 86400);
         return `${days}天`;
     }
     return "十天以上";
 };

 return (
     <div className='timer'>{formatTime(Math.floor(currentTime / 1000))}</div>
 )