前言
让我们回想一下关于定时器的内容,我们只知道,他是在我们设置的时间后才异步执行的程序。
可在面试时回答这个就够了吗?那当然是不够的。
本文将带你深入了解定时器底层
定义
我们先了解什么是定时器。
在JavaScript 中,定时器是一种用于延迟执行代码或定期执行代码的机制。它们允许开发者控制代码执行的时间点,从而实现诸如动画、轮询服务器、设置等待时间等功能。而定时器也分为两类setTimeout
和setInterval
。
执行原理
那么定时器的执行原理是什么呢?
我们都知道,JavaScript是单线程,只有一条主线程来处理任务,而我们的定时器则是可以异步执行的,在主线程任务结束后,才轮到我们的定时器执行。执行的都是我们的回调函数,放入处理异步函数的核心机制Event loop(事件循环)
中,决定了什么时候执行代码。
总的来说,就是当程序碰到定时器时,会将其放入事件循环中,先忽略掉,待主线程内其他任务完成时,再执行定时器任务。
setTimeout
setTimeout
是 JavaScript 中用于延迟执行代码的内置函数。它允许你指定一段代码(通常是一个函数)在经过给定的时间间隔后执行。这个时间间隔是以毫秒为单位的,1000 毫秒等于 1 秒。也是最常见的定时器用法。
例子有:
javascript
const timeout= setTimeout(function(){
console.log('abcdefg');
},10000)
console.log(123)
先输出123,之后等待十秒钟左右,才会进行定时器输出。
最小时间
假如我将延迟设置为0ms,那么定时器会立即执行吗?
答案是否定的。定时器并不会立即执行
依旧是先输出123
不对啊,我把延迟时间设置为0,按道理来说不应该是按照文档流,从上到下依次执行吗?怎么先输出的还是123呢?
有两个方面:
-
在定时器运行的时候,浏览器有一个延迟时间的最小值,为4ms。意味着即使你把延迟时间设置为0,实际的延迟也会有4ms。而在node环境中,延迟为1ms。
-
前面提到了,定时器是一个异步执行的程序。必须在主线程程序执行完毕后,才轮到了异步执行。所以这里先运行了主程序的输出
console.log(123)
,随后才运行了我们的定时器,所以123才会先输出。运行也有一个主次之分。
定时器一定会运行吗?
这我们又可以衍生到另一个问题。
是不是在我们的延迟时间到了后,一定会执行我们的定时器函数呢??
答案也是否定的。
javascript
const timeout= setTimeout(function(){
console.log('abcdefg');
},0)
while(true){ // 死循环
}
console.log('123')
此时我们的页面会一直处于加载状态。
前面也说了,JavaScript是单线程的,如果主线程上的程序任务运行时间超过了延迟时间
或主线程任务陷入死循环等情况。
在JavaScript中,所有程序在运行时都在同一个调用栈上按顺序执行。而定时器就排在主程序后面。如果主程序一直没有运行完毕,那么永远也轮不到定时器执行。所以并不是到了延迟时间后就会执行。
setInterval
这是定时器的另一个类型。他会在每次延迟时间后重复的运行定时器函数
javascript
const interval= setInterval(function(){
console.log('abcdefg');
},1000)
console.log('123')
此时我们会得到
左边的40
就是他的运行次数。
那么是不是一直执行有些妨碍我们的任务。那我们怎么停止他呢?
xml
<body>
<button id= "btn"> 关闭计时器 </button>
<script>
const btn = document.getElementById('btn')
btn.addEventListener('click',function(){
clearInterval(interval)
})
const interval=setInterval(function(){
console.log('qa');
},1000)
console.log(123)
</script>
</body>
一般我们用clearInterval
和clearTimeout
来结束我们的定时器。这里我们还借用了按钮,可以看到我们的执行效果后再关闭定时器。一般都是返回我们的定时器ID。
我们还可以用setTimeout
来结束setInterval
。
//
let intervalId = setInterval(() => {
console.log('This is a periodic message');
}, 1000);
// 使用 setTimeout 在 5 秒后停止 setInterval
setTimeout(() => {
console.log('Stopping the interval after 5 seconds...');
clearInterval(intervalId);
console.log('Interval has been cleared.');
}, 5000);
手搓定时器
但在面试的时候,面试官可能会要求我们用setTimeout来实现setInterval的重复运行效果。 那我们应该怎么做呢?
首先我们应该想的的是:递归。 一直调用自己,不就是重复运行的效果吗?怎么才能实现呢?
先给源码:
scss
function customSetInterval (fn,time){
let intervalid =null;
function loop(){
// 递归循环,类似setInterval
intervalid = setTimeout(()=>{
fn();
loop();
},time)
}
loop();
// 清理定时器
return () => clearTimeout(intervalId)
}
const interval = customSetInterval(function(){
console.log('手搓定时器')
},1000)
我们得到效果:
完美实现了setInterval的效果。
首先定义一个变量 intervalid,用于保存 setTimeout 返回的定时器 ID。
使用 setTimeout 设置一个延迟执行的回调函数。
每次回调函数执行时,都会调用传入的 fn 函数,并在 fn 执行完毕后再次调用 loop
从而形成一个循环,类似于 setInterval 的周期性调用。
然后用clearTimeout来清理定时器
然后就是使用实例了,每隔一秒生成一个手搓定时器
。 如果想停止,只需要用setTimeout停止。在五秒后停止生成
scss
setTimeout(()=>{
interval()
},5000)
在复习一下前面,此时也只生成了4次而不是5次。
是因为主程序运行也花费了一些时间,生成5次的时间不够。
结语
这就是定时器的底层运行,这样回答。面试官包给满分。