【JS】2307- setTimeout是准时的吗?

本文转载于稀土掘金技术社区,作者:半壳椰子

https://juejin.cn/post/7420059840971980834

引言

最近在一些论坛上,有人讨论 setTimeout 的准确性。因此,我进行了探索,以解答这个问题。结果发现,setTimeout 并不完全可靠,因为它是一个宏任务。所指定的时间实际上是将任务放入主线程队列的时间,而不是任务实际执行的时间。

`setTimeout(callback, 进入主线程的时间)`

因此,何时执行回调取决于主线程上待处理的任务数量。

演示

这段代码使用一个计数器来记录每次 setTimeout 的调用。设定的间隔时间乘以计数次数,理想情况下应等于预期的延迟。通过以下示例,可以检查我们计时器的准确性。

function time () {
    var speed = 50, // 间隔
    count = 1// 计数
    start = newDate().getTime();

    function instance() {
        var ideal=(count * speed),
        real = (newDate().getTime() - start);

        count++;
        console.log(count + '理想值------------------------:', ideal); // 记录理想值
        console.log(count + '真实值------------------------:', real); // 记录理想值

        var diff = (real - ideal);
        console.log(count + '差值------------------------:', diff); // 差值
        
        // 小于5执行
        if (count < 5) {
            window.setTimeout(function(){
                instance();
            }, speed);
        };
        
    };

    window.setTimeout(function () {
        instance();
    }, speed);
};
打印1
4c48107a7c52fbd69a8886c825210358.jpeg

我们可以在 setTimeout 执行之前加入额外的代码逻辑,然后再观察这个差值。

...
window.setTimeout(function(){
    instance();
}, speed);
for(var a = 1, i = 0; i < 10000000; i++) { 
    a *= (i + 1); 
};
...
打印2
b3ab3790009f630c288b282be6fc6761.jpeg

可以看出,这大大增加了误差。随着时间的推移,setTimeout 实际执行的时间与理想时间之间的差距会不断扩大,这并不是我们所期望的结果。在实际应用中,例如倒计时和动画,这种时间偏差会导致不理想的效果。

a3f5b7bddd8567407a3b83c0c13a3c41.jpeg

如何实现更精准的 setTimeout

requestAnimationFrame

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行,回调函数执行次数通常是每秒60次,也就是每16.7ms 执行一次,但是并不一定保证为 16.7 ms。

我们用requestAnimationFrame模拟 setTimeout

function setTimeout2(cb, delay) {
    const startTime = Date.now();

    function loop() {
        const now = Date.now();
        if (now - startTime >= delay) {
            cb();
        } else {
            requestAnimationFrame(loop);
        }
    }

    requestAnimationFrame(loop);
};
打印3
22e44b988f40c2e2cbab4eeba78626ca.jpeg

貌似误差问题还是没有得到解决,因此这个方案还是不行。

while

想得到准确的,我们第一反应就是如果我们能够主动去触发,获取到最开始的时间,以及不断去轮询当前时间,如果差值是预期的时间,那么这个定时器肯定是准确的,那么用while可以实现这个功能。

function time2(time) {
    const startTime = Date.now();
    
    function checkTime() {
        const now = Date.now();
        if (now - startTime >= time) {
            console.log('误差', now - startTime - time);
        } else {
            setTimeout(checkTime, 1); // 每毫秒检查一次
        }
    }

    checkTime();
}

time2(5000);

误差存在是 2, 甚至为 0, 但使用 while(true) 会导致 CPU 占用率极高,因为它会持续循环而不进行任何等待,会使得页面进入卡死状态,这样的结果显然是不合适的。

setTimeout 系统时间补偿

这个方案是在 Stack Overflow 看到的一个方案,我们来看看此方案和原方案的区别。

当每一次定时器执行时后,都去获取系统的时间来进行修正,虽然每次运行可能会有误差,但是通过系统时间对每次运行的修复,能够让后面每一次时间都得到一个补偿。

function time () {
    var speed = 50, // 间隔
    count = 1// 计数
    start = newDate().getTime();

    function instance() {
        var ideal=(count * speed),
        real = (newDate().getTime() - start);

        count++;
        console.log(count + '理想值------------------------:', ideal); // 记录理想值
        console.log(count + '真实值------------------------:', real); // 记录理想值

        var diff = (real - ideal);
        console.log(count + '差值------------------------:', diff); // 差值
        
        // 5次后不再执行
        if (count < 5) {
            window.setTimeout(function(){
                instance();
            }, (speed - diff));
        };
    };

    window.setTimeout(function () {
        instance();
    }, speed);
};
打印4
7fdc78b76e78be739c509b90214d471d.jpege43b863e1c41a79788c36fd2b072eb7f.jpeg

结论

多次尝试后,是非常稳定的,误差微乎其微,几乎可以忽略不计,因此通过系统的时间补偿,能使 setTimeout 变得更加准时。

<think>我们正在讨论JavaScript中的setTimeout函数。用户询问的是setTimeout的"默认更新时间"。从引用内容来看,用户可能误解了setTimeout的机制。setTimeout并不是用来“更新”时间的,而是用来设置一个定时器,在指定的延迟后执行一次函数。根据引用[2]:setTimeout()只执行一次,setInterval()会重复执行。所以setTimeout本身并没有“更新”机制,它只执行一次。如果要实现定时更新(比如每秒更新一次时间),通常有两种方法:1.使用setTimeout递归:在每次执行函数时,再次设置一个新的setTimeout定时器,这样就可以实现循环执行。2.使用setInterval:设置一个间隔时间,每隔一段时间执行一次。另外,引用[1]提到:将整个静止时间js定义成一个函数refreshTime()方法;然后使用setTimeout(refreshTime,1000)来实现定时刷新。这里实际上就是递归使用setTimeout。关于用户的问题“settimeout默认更新时间”:实际上,setTimeout没有所谓的“默认更新时间”。setTimeout的第一个参数是回调函数,第二个参数是延迟的时间(单位是毫秒)。如果第二个参数省略,则默认值为0。但要注意,即使设置为0,也不代表立即执行,而是会被放入事件队列,等待当前执行栈清空后才执行。引用[3]中提到了延迟时间为0的setTimeoutsetTimeout(function(){...},0);这常用于将代码推迟到下一个事件循环执行。引用[4]提到:违规长时间运行的JavaScript任务需要<N>ms。这里涉及到JavaScript的事件循环机制,长时间运行的任务可能会阻塞事件循环,导致setTimeout的回调不能准时执行。因此,回答用户的问题:setTimeout没有默认的更新时间,它的延迟时间是由开发者显式指定的。如果不指定,则默认为0毫秒。但是,用户可能真正想知道的是如何用setTimeout实现定时更新(比如每秒更新一次时间)。我们可以提供示例代码。此外,根据系统级指令,我们需要在回答中自然地添加引用标识,并且生成相关问题。回答结构:1.解释setTimeout的延迟时间参数,说明默认值为0。2.说明setTimeout本身只执行一次,如果要重复执行,需要递归调用或使用setInterval。3.给出一个使用setTimeout实现定时更新的示例(比如更新页面上的时间)。4.生成相关问题。注意:用户的问题可能源于对“更新时间”的误解,我们需要澄清。</think>### JavaScript 中 `setTimeout` 的默认更新时间解析 `setTimeout()` 函数在 JavaScript 中**没有默认的"更新时间"概念**,其核心行为是通过**延迟执行回调函数**实现的。关键点如下: 1. **默认延迟时间** - 当省略第二个参数时,`setTimeout(callback)` 的**默认延迟时间为 `0` 毫秒**(非立即执行)[^3]。 - 例如: ```javascript setTimeout(() => console.log("执行"), /* 默认延迟 0ms */); ``` 2. **执行机制与"更新"无关** - `setTimeout()` **仅执行一次回调**,不自动重复(与 `setInterval()` 不同)[^2]。 - 实现"定时更新"需手动递归调用: ```javascript function refreshTime() { console.log("更新时间:", new Date()); setTimeout(refreshTime, 1000); // 每秒递归调用 } refreshTime(); // 启动更新 ``` 3. **时间精度问题** -JavaScript 事件循环影响,回调的实际执行时间**可能晚于设定延迟**(尤其主线程阻塞时)[^4]。 - 示例: ```javascript setTimeout(() => console.log("延迟 100ms"), 100); // 若主线程繁忙,实际执行可能 >100ms ``` 4. **与 `setInterval()` 的区别** | **特性** | `setTimeout` | `setInterval` | |----------------|----------------------------------|--------------------------------| | **执行次数** | 单次 | 重复 | | **延迟累积** | 无累积问题(递归调用可控延迟) | 可能累积延迟导致回调堆积 [^2] | | **适用场景** | 单次延迟、需动态调整间隔的更新 | 固定间隔的重复任务 | ### 总结 - ⚠️ `setTimeout` **没有内置"更新时间"**,其默认延迟为 `0ms`。 - 🔁 实现定时更新需**递归调用**(如每秒刷新时间)。 - ⏱️ 实际执行时间受事件循环制约,非严格实时 [^4]。 --- ### 相关问题 1. `setTimeout` 延迟设为 `0` 时有哪些特殊应用场景? 2. 如何避免 `setInterval` 的延迟累积问题? 3. 事件循环机制如何影响 `setTimeout` 的执行精度? 4. 递归 `setTimeout` 与 `setInterval` 的性能差异有哪些? [^1]: 引用[1]: 但此时页面显示的时间是静止的,这里要用setTimeout()或者setInterval()实现定时刷新。将整个静止时间js定义成函数refreshTime()方法;setTimeout(refreshTime,1000) [^2]: 引用[2]: setTimeout()和setInterval()主要有以下区别: setTimeout()只执行一次,setInterval()会重复执行;setInterval()可能出现累积延迟的问题 [^3]: 引用[3]: 延迟时间为0的setTimeoutsetTimeout(function(){ ... }, 0); [^4]: 引用[4]: 违规长时间运行的JavaScript任务需要<N>ms。JavaScript的异步性质可能导致回调延迟执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值