前言
想要用 setTimeout 实现 setInterval , 有几个关键点需要明白 :
- setTimeout 基础知识
- setInterval 基础知识
- setTimeout 和 setInterval 有什么关系 ?
工欲善其事 , 必先利其器 , 我相信 , 看完这篇文章你一定可以手撕 !
当然 , 如果已经很锋利 , 请各位大佬移步至文末 , 亲自手撕 , 一睹大佬手撕的风采 !
setTimeout & setInterval 对比
这二者都是负责处理延时任务或循环任务的 JavaScript 函数,各有各的特点和应用场景:
补充 : requestAnimationFrame:专门用于动画,在下一次重绘之前执行代码,使动画更加平滑和高效。
参数结构
两者都接受相似的参数:
- setTimeout(callback , time [,arg1,arg2,...])
- setInterval(callback , time [,arg1,arg2,...])
- callback:当计时器到期时要执行的函数。
- time:对于
setTimeout是延迟时间,对于setInterval是每次执行之间的间隔时间,以毫秒为单位。非数字值会被视为0,负数也会被当作0处理。 - [arg1, arg2, ...] :可选参数,这些参数将在调用回调函数时传递给它。
返回值
setTimeout和setInterval都返回一个定时器 ID,这是一个非零整数值,代表已创建的计时器。这个返回值一般用于取消对应的定时器
功能对比
setTimeout:用来设定一个计时器,在指定的时间(毫秒)后仅执行一次给定的回调函数。setInterval:类似于setTimeout,但它会在每隔指定的时间间隔重复执行给定的函数,直到被明确中止 ,适用于需要定期执行的任务。
setTimeout
它很适合在一段时间后执行某个操作。
eg : 模态框的自动关闭,轻松实现一次性的延时任务。
要注意的是,时间可能不会精确到设定的毫秒数,因为 JavaScript 是单线的,其他任务也可能会影响到它的执行时间。
举个栗子
setTimeout(() => {
console.log("This runs once after 1 second.");
}, 1000);
一些其他因素 , 在 1.332 s 后打印
setInterval
再看看 setInterval。这个函数特别适合定时刷新数据、轮询服务器或者是做一些每隔一段时间就需要重复的事情。
需要注意内存泄漏和定时器的清除,最好在使用完之后调用 clearInterval() , 并传入定时器的 ID 。
(关于这个 clearInterval() 我们在浏览器中 , 做个小实验 😁)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">关闭定时器</button>
<script>
const interval=setInterval(function(){
console.log('hello world');
},1000)
console.log(interval)
const btn=document.getElementById('btn');
btn.onclick=function(){
clearInterval(interval);
}
</script>
</body>
</html>
在 node 中做个小测试 , 检验下重复性
const intervalId = setInterval(() => {
console.log("This runs every 2 seconds.");
}, 2000);
// Clear the interval after 10 seconds
setTimeout(() => {
clearInterval(intervalId);
console.log("Interval cleared.");
}, 10000);
每隔两秒 , 打印"This runs every 2 seconds." , 10 s 后清除定时器。
补充
最后说说 requestAnimationFrame。这个函数与前两者不同,它是为了提升动画效果而设计的,因为它会在浏览器下一次重绘之前执行回调。这可以使动画更加平滑,与屏幕刷新率同步。适合用来制作高性能动画。
示例代码:
function animate() {
// 动画代码
console.log("Animating...");
// 下一帧继续动画
requestAnimationFrame(animate);
}
// 开始动画
requestAnimationFrame(animate);
另外,requestAnimationFrame 有个优点是在页面隐藏或最小化时会暂停执行,这对于省电和性能优化都很有好处。
深入理解定时器的运行机制
由于 JavaScript 是单线程语言,它只有一个主线程,所有任务都在这个线程上执行,这意味着一次只能做一件事。为了处理异步操作,比如计时器、网络请求等,JavaScript 采用事件循环(Event Loop)机制 , 当一个异步操作完成时,它会被放入回调队列中等待主线程空闲时再执行。
看下面的栗子 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>定时器</title>
</head>
<body>
<script>
setTimeout(function(){
console.log('-------');
},10000)
console.log(123)
</script>
</body>
</html>
执行setTimeout的时候 , 先把callback 函数 放入事件队列 ,当主线程全部执行完后,再来执行放入事件队列中的callback 函数。 所以先打印主线程中的123 , 在打印 "-------" 。
这就是 setTimeout 的异步性 , 因此即使setTimeout()方法指定了一个很短的时间,它也不会在调用代码之后立即执行 , 因为和上面举的栗子一样 , 执行定时器的时候 , 它callback 函数 , 放在事件队列的末尾,直到事件队列中没有任何待处理的任务,才会执行。
因此,我们可以知道 , 当代码块执行时,当前执行的上下文(也称为堆栈)已经被清空。
如果setTimeout() 方法在代码块在执行之前被清除或者代码块执行时间过长,那么代码块将会在JavaScript引擎空闲时尽快被执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>定时器</title>
</head>
<body>
<button id="btn2">停止</button>
<script>
setTimeout(function(){
console.log('hello world');
},1000);
while(true){
}
</script>
</body>
</html>
差点把我浏览器搞崩溃了 ......
这个例子也说明 , setTimeout()方法不是一个精确的时间控制器,而是一个粗略的时间控制器。如果需要更精确的时间控制器,可以考虑使用 requestAnimationFrame()或 Web Workers.
接下来就去手撕面试题吧 🤡 !
题目
用 setTimeout 实现 setInterval
题目分析
在 JavaScript 中,setInterval 和 setTimeout 是用于管理时间的两个基本方法。
- setInterval 会按照固定的时间间隔反复执行某个函数,
- 而 setTimeout 在指定时间后执行一次指定的函数。
在这道题中,要求使用 setTimeout 来模拟 setInterval 的功能。
因此,我们需要让 setTimeout 在其回调函数中再次调用自身,从而实现类似 setInterval 不断调用的效果。
实现思路
1) 定义一个函数,该函数将执行我们需要周期性运行的任务。
2) 在这个函数内,先执行任务逻辑,然后使用 setTimeout 调用自身,再次设定下一次执行的时间。
3) 将初始调用放在一个主函数(或直接调用该函数),从而触发首次定时操作。
实现代码
function customSetInterval(callback, interval) {
function intervalFunction() {
callback();
setTimeout(intervalFunction, interval);
}
setTimeout(intervalFunction, interval);
}
var f = () => {
console.log('模拟的 setInterval,定时任务执行中...');
}
// 使用 customSetInterval 模拟 setInterval
customSetInterval(f, 1000);
代码解释:
1) 定义了名为 customSetInterval 的函数,该函数接收两个参数:callback 和 interval。callback 是要周期性执行的任务,interval 是两次任务执行之间的时间间隔。
2) 在 customSetInterval 内部,定义了一个名为 intervalFunction 的函数。在 intervalFunction 中,首先调用 callback 函数来执行具体的任务,然后使用 setTimeout 调用 intervalFunction 本身,并设定相同的时间间隔 interval。这使得 intervalFunction 可以不断循环执行,从而模拟 setInterval 的效果。
3) 在 customSetInterval 的最后,首次调用 setTimeout(intervalFunction, interval) 来启动循环。
另一种写法 :
function customSetTimeout(fn,time){
let intervalID=null
function loop(){
intervalID= setTimeout(()=>{
fn();
loop();
},time)
}
loop();
return ()=>clearTimeout(intervalID);
}
const interval=customSetTimeout(function(){
console.log('hello world')
},1000)
// setTimeout(()=>{
// interval();
// },5000)
总结
虽然 setTimeout 可以用来模拟 setInterval,但两者有一些不同之处,了解这些差异有助于更好地使用定时器功能。
1) setInterval 的确切时间间隔:setInterval 是在任务开始执行的时间点计时,因此如果任务执行时间较长,会影响下一个任务的时间间隔。从而导致任务执行频率降低。
2) setTimeout 的灵活性:使用 setTimeout 模拟 setInterval 更具灵活性,因为每次递归调用可以动态调整时间间隔。例如,如果需要在任务执行后动态决定下一次执行的时间,可以根据业务逻辑调整 setTimeout 的时间参数。
3) 清除定时器:无论使用 setInterval 还是 setTimeout,清除定时器对性能优化以及防止内存泄漏都是非常重要的一个功能。
- 对于 setInterval,使clearInterval;
- 对于 setTimeout,使用 clearTimeout。
阁下 , 手撕否 ? 请在评论区说一声呵呵 🤡