前言:
上一世,我站在了梦想的门前——一家心仪已久的大厂就在眼前。然而,命运似乎喜欢开玩笑,在最后一轮面试中,面试官抛出了一个看似简单却足以改变一切的问题:“你能用 setTimeout
实现 setInterval
吗?”那一瞬间,我的心跳仿佛停滞,脑海中一片空白。最后,我只能眼睁睁地看着这份机会溜走,带着遗憾和不甘,噢不!我的大厂梦寄你太美了。
这一世,我重活一遍,决不能在这里倒下,面试官,你才是挑战者!
简述:
本文将详细讲解:定时器中的setTimeout
和setInterval
,以及它们的运行顺序,和最后的压轴面试题:使用 setTimeout 来实现 setInterval 。我会先介绍完基础知识,最后连串起来手撕面试题,如果兄弟们等不及的可以直接跳到最后看如何实现。
定时器:
JavaScript 是单线程语言,它只有一个主线程,所有任务都在这个线程上执行,这意味着一次只能做一件事。为了处理异步操作,比如计时器、网络请求等,JavaScript 使用了事件循环(Event Loop)机制。当一个异步操作完成时,它会被放入回调队列中等待主线程空闲时再执行。
setTimeout 和 setInterval 函数:
之所以把它们放在一起讲,是因为其实它的功能有点相似,一起讲便于大家更好的理解。
功能对比
setTimeout
:用来设定一个计时器,在指定的时间(毫秒)后仅执行一次给定的回调函数。setInterval
:类似于setTimeout
,但它会在每隔指定的时间间隔重复执行给定的函数,适用于需要定期执行的任务。
参数结构
两者都接受相似的参数:
- callback:当计时器到期时要执行的函数。
- time:对于
setTimeout
是延迟时间,对于setInterval
是每次执行之间的间隔时间,以毫秒为单位。非数字值会被视为0
,负数也会被当作0
处理。 - [arg1, arg2, ...] :可选参数,这些参数将在调用回调函数时传递给它。
返回值
setTimeout
和setInterval
都返回一个定时器 ID,这是一个非零整数值,代表已创建的计时器。这个返回值一般用于取消对应的定时器
如何理解setTimeout 是异步执行的计时器,会在主线程执行完之后再执行?
我们可以通过一段代码来解释:
<!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('hello world');
},10000)
console.log(123)
</script>
</body>
</html>
大家可以猜猜结果:
123 hello world
解释: 兄弟们肯定有疑问,为什么不是先执行定时器,再输出123呢?这样的想法也没错,JS执行机制:从上到下嘛。但是换个思想,假如说:定时器的时间是接近于无限长,如果先执行定时器,那么后面的程序是不是永远不可能得到运行了。
显然开发者不会允许这种情况发生,那么setTimeout
,它的运行其实是异步的,也就不是从上到下,而是先把callback 函数
放入 event loop
,然后当主线程全部执行完后,再来执行放入 event loop
中的callback 函数
。
所以也就引出了一个问题:是否定时器会按时输出函数中的内容呢?
setTimeout一定会在指定时间后执行吗?
答案当然是否定的!为什么呢?
- 当你的主线程程序是无限次的循环时,那么主线程没有结束,就不会去执行定时器中的回调函数了。
比如:
<!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>
- 就是如果你在网页中打开,定时器启动但是你在未达到定时器时间时,就关闭了网页,这样也执行不了。
如何关闭定时器呢?
关闭定时器,这时候就要用到定时器自带的返回值了。
<!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>
运行以下代码,我们可以看到结果:
我们可以看到,当没点击停止按钮时,
hellow world
一直在输出,点击后就停止了。
流程分析:
- 设置定时器:在
<script>
标签内的代码被执行时,首先通过setInterval
函数创建了一个定时器。这个定时器每秒(1000毫秒)都会触发一次匿名函数,该匿名函数的作用是向浏览器的控制台输出字符串'hello world'。setInterval
返回一个唯一的标识符(ID),用来标识这个定时器,这里被存储在变量interval
中。 - 打印定时器ID:紧接着,
console.log(interval)
这行代码将定时器的ID输出到控制台。 - 获取按钮元素:使用
document.getElementById('btn')
获取页面上的按钮元素,并将其赋值给btn
变量。 - 为按钮添加点击事件处理器:为
btn
按钮设置了点击事件处理函数。当用户点击按钮时,会调用clearInterval(interval)
来停止定时器,从而阻止'hello world'继续被输出到控制台。 - 用户交互:一旦页面加载完毕,如果用户点击了“关闭定时器”按钮,就会触发上述的点击事件处理函数,定时器将会被清除,之后就不会再有'hello world'的消息输出到控制台了。
当我们拿捏了以上的知识点,我们就可以开始解面试题了。
面试题
如何 setTimeout 实现 setInterval
首先我们来分析一下:这是一道场景编程题,要求我们去使用setTimeout
,然后实现setInterval
,当时我看到这题的时候,想到的是使用setTimeout
去递归自己,但是好像不太行。
好了来上代码吧!
<!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>
<script>
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)
</script>
</body>
</html>
流程分析:
- 定义
customSetTimeout
函数:
customSetTimeout
接受两个参数:一个回调函数fn
和一个时间间隔time
(以毫秒为单位)。- 定义了一个内部变量
intervalID
,用于存储当前setTimeout
的ID。 loop
函数是一个递归函数,它设置一个setTimeout
,在指定的时间间隔后执行传入的fn
函数,并再次调用自身以保持循环。
- 启动首次循环:
- 当
customSetTimeout
被调用时,它立即调用了loop()
,这会开始第一次定时器计时。当定时器到期时,fn
将被执行,并且loop
会再次被调用,形成一个无限循环。
- 返回清除定时器的函数:
- 最后的返回值是点睛之笔:返回了一个函数基于当前
setTimeout
的ID,而关闭相应的定时器的函数。运行该函数,从而终止循环,
- 使用
customSetTimeout
函数:
- 创建了一个名为
interval
的常量,它实际上是指向customSetTimeout
返回的清除定时器函数的引用。 - 使用
customSetTimeout
设置了每秒执行一次的定时任务,每次执行时都会输出'hello world'到控制台。
- 设定定时器以停止
customSetTimeout
:
- 使用
setTimeout
设置了一个新的定时器,在5秒(5000毫秒)后触发。 - 当这个新的定时器到期时,它会调用
interval()
,即之前由customSetTimeout
返回的函数,用来清除由customSetTimeout
创建的无限循环,终止定时任务。
总结:
如此一来,我们就实现了使用使用 setTimeout
来实现 setInterval
,并且定时的关闭了这个功能。
结语:
分享知识的时间总是短暂的,如果对你有帮助,也请给我点个赞吧!