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

前言

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

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

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

定义

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

在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次的时间不够。

结语

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

相关推荐
uhakadotcom4 分钟前
Apache Airflow入门指南:数据管道的强大工具
算法·面试·github
树上有只程序猿16 分钟前
后端思维之高并发处理方案
前端
uhakadotcom16 分钟前
Ruff:Python 代码分析工具的新选择
后端·面试·github
uhakadotcom19 分钟前
Mypy入门:Python静态类型检查工具
后端·面试·github
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox1 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞1 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行1 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758101 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox