面试考题:定时器底层逻辑

前言

让我们回想一下关于定时器的内容,我们只知道,他是在我们设置的时间后才异步执行的程序。

可在面试时回答这个就够了吗?那当然是不够的。

本文将带你深入了解定时器底层

定义

我们先了解什么是定时器。

在JavaScript 中,定时器是一种用于延迟执行代码或定期执行代码的机制。它们允许开发者控制代码执行的时间点,从而实现诸如动画、轮询服务器、设置等待时间等功能。而定时器也分为两类setTimeoutsetInterval

执行原理

那么定时器的执行原理是什么呢?

我们都知道,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呢?

有两个方面:

  1. 在定时器运行的时候,浏览器有一个延迟时间的最小值,为4ms。意味着即使你把延迟时间设置为0,实际的延迟也会有4ms。而在node环境中,延迟为1ms。

  2. 前面提到了,定时器是一个异步执行的程序。必须在主线程程序执行完毕后,才轮到了异步执行。所以这里先运行了主程序的输出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>

一般我们用clearIntervalclearTimeout来结束我们的定时器。这里我们还借用了按钮,可以看到我们的执行效果后再关闭定时器。一般都是返回我们的定时器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次的时间不够。

结语

这就是定时器的底层运行,这样回答。面试官包给满分。

相关推荐
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
LCG元6 小时前
【面试问题】JIT 是什么?和 JVM 什么关系?
面试·职场和发展
迷雾漫步者6 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖9 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235249 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_7482402510 小时前
前端如何检测用户登录状态是否过期
前端