setTimeout与setInterval的坑以及优缺点

本文解释了JavaScript中setTimeout与setInterval的区别,特别是它们在事件循环中的行为差异。通过实例展示了两者如何处理回调函数及定时任务的执行时机。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说到setTimeout与setInrerval大家可能都觉得很easy,我刚接触js的也是这样的想法,可后来在知乎看到了一道题,大概好像是这样的:

例一:

setTimeout(function(){

console.log("小马“);

setTimeout(function(){arguments.callee;},1000);

},1000)

例二:

setInterval(function(){console.log("小马“);},1000);

问一与二的区别?

说实话我刚看到这个题目的时候是懵比的,因为我觉得这两者是完全一样的呀,都是间隔1000ms之后执行回调的呀,可是既然这么问了肯定他们之间是有区别的,于是乎我就去查了相关的资料,果然,二者不仅仅是有差别的,而且定时器也颠覆了我以往的认知。

首先,这两个定时器的基本含义我就不重复了,我觉得只要是个学前端的肯定没有不知道的。为什么说定时器也颠覆了我以往的认知呢?因为我发现定时器的回调函数并不是相当于在时间到了就执行,而是有一个主js执行进程,这个进程是页面刚加载的时候页面按照加载顺序执行的js代码,此外还有一个需要在进程空闲的时候执行的代码队列,而我们所说的定时器的回调就是相当于(以上的例一为例)在1000ms之后把定时器回调放入到空闲队列中(注意,空闲队列有可能还有其它的代码,比如点击事件,因此定时器回调放入的位置不一定是空闲队列的开始位置!)举个例子:

 var i=0;

function a(){

t=setTimeout(function(){console.log("小明")},0);

}

a();

alert(”小红“);

 此时你会发现先弹出小红,又弹出的小明!!

好了,简单的可以理解位定时器和js其他程序是并行执行的,不过jquery的作者有一篇文章专门介绍这个队列的,有兴趣的可以搜一下看看!!

接下来说第二点,就是例一与例二的区别:

setInterval有个很烦的地方就是当js主程序空闲时候,执行代码队列里面的代码的时候,如果此时候我们有一个问题,定时器是等到回调执行完,才开始计时进行下次循环呢?还是只要一次计时完毕,插入回调之后不管回调执不执行就开始计时呢?答案显然是后者,这也就是我说setInterval坑比的原因啊,因为这会出现一种情况,当我们插入回调的时候前队列有别的代码在执行,这时候回调肯定是不会执行的,因此如果这个时候无限定时时间到了会再次插入回调,这个时候如果发现队列中的第一次回调没有执行,那么再次插入的回调浏览器就默认取消,(这是以防出现回调连续执行多次的情况)但是这又引发了新的情况就是有些回调是不能取消掉的?

这就是我们经常使用例一代替例二的原因,例一可以避免上述的情况。累,很简单的东西被我说的这么复杂,我也是醉了,看来文字功底还很欠缺,可是我的梦想是新时代的作家,这可咋整呢?

<think>嗯,用户问如何用setTimeout实现setInterval的效果。首先,我需要回忆一下这两个函数的区别。setInterval会按照指定的时间间隔重复执行函数,而setTimeout只会执行一次。但有时候setInterval可能会有问题,比如如果回调执行时间比间隔长,就会导致多个回调堆积。所以用setTimeout模拟可能更可控。 那具体怎么做呢?应该是在每次setTimeout的回调中再次调用setTimeout,形成一个循环。比如,定义一个函数,里面执行目标函数,然后设置一个新的setTimeout来调用自身。这样就能实现间隔执行的效果。 然后,用户可能还需要停止这个循环,所以应该返回一个清除的函数,或者类似clearInterval的机制。比如,在每次设置setTimeout的时候,保存返回的timer ID,然后提供一个方法来清除它。 举个例子,写一个函数mySetInterval,接受回调函数和间隔时间。在内部定义一个wrapper函数,里面执行回调,然后设置setTimeout调用wrapper,同时保存新的timer ID。这样每次都是在上一次回调结束后才设置下一次,避免了间隔重叠的问题。 还要考虑如何停止这个循环。可以返回一个对象,里面有一个clear方法,用来清除当前的timer。或者在每次设置timer时更新一个变量,这样clear的时候可以取消最新的timer。 另外,可能需要处理错误,确保即使回调抛出异常,后续的定时器仍然能够继续。不过通常可能不需要,因为用户希望出错时停止?或者应该用try-catch包裹回调? 再想想,用户可能想要的是准确的间隔,还是每次回调执行完毕后再等间隔时间。用setTimeout模拟的话,是后者,即间隔是从回调结束开始计算的。而setInterval是固定间隔,不管回调执行时间。这点需要说明清楚。 然后,代码示例。比如: function mySetInterval(callback, interval) { let timerId; function repeat() { callback(); timerId = setTimeout(repeat, interval); } timerId = setTimeout(repeat, interval); return { clear: () => clearTimeout(timerId) }; } 这样每次执行完callback后,再设置下一次的setTimeout。然后返回一个clear方法,可以清除当前的定时器。 然后,用户可能还需要处理参数传递,比如如果回调需要参数的话。这时可以在repeat函数里调用callback.apply或者用箭头函数传参。 另外,可能需要处理首次执行是否需要立即执行。比如setInterval是在间隔后第一次执行,而用setTimeout模拟也是同样的行为。如果用户想要立即执行,可以调整代码,先调用一次callback,再设置定时器。 但原题是模拟setInterval,所以应该保持行为一致,即首次执行在间隔之后。 总结步骤: 1. 使用递归的setTimeout调用。 2. 每次执行回调后设置下一次的定时器。 3. 提供清除定时器的方法。 4. 处理可能的参数传递和错误情况。 然后解释为什么这样做更好,比如避免了setInterval可能出现的回调堆积问题,因为下一次的执行总是在前一次完成之后才被安排。 相关问题可能涉及setTimeoutsetInterval的区别,递归setTimeout优缺点,如何清除定时器,或者如何处理异步回调的情况。</think><< 可以通过递归调用 `setTimeout` 来模拟 `setInterval` 的效果。以下是实现方法: ```javascript function customInterval(callback, interval) { let timerId = null; function repeat() { callback(); timerId = setTimeout(repeat, interval); // 递归调用 } timerId = setTimeout(repeat, interval); // 首次启动 return { // 返回清除方法 clear: () => clearTimeout(timerId) }; } // 使用示例 const interval = customInterval(() => { console.log("执行"); }, 1000); // 5秒后停止 setTimeout(() => interval.clear(), 5000); ``` **实现原理**: 1. **递归调用**:每次 `setTimeout` 执行完毕后,再次调用自身形成循环。 2. **避免重叠**:原生 `setInterval` 不同,这种方式能确保前一次回调完成后再启动下一次计时。 3. **控制停止**:通过返回的 `clear()` 方法清除定时器。 **原生 setInterval 的缺陷**: - 如果回调执行时间超过间隔时间,会导致多个回调堆积。 - 无法动态调整下一次调用的间隔时间。 **递归 setTimeout 的优势**: - 每次间隔从前一次回调结束时开始计算。 - 更灵活控制执行流程(如动态调整间隔)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值