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

前言

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

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

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

定义

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

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

结语

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

相关推荐
01_几秒前
力扣hot100——LRU缓存(面试高频考题)
leetcode·缓存·面试·lru
祈澈菇凉12 分钟前
如何结合使用thread-loader和cache-loader以获得最佳效果?
前端
垣宇15 分钟前
Vite 和 Webpack 的区别和选择
前端·webpack·node.js
java1234_小锋18 分钟前
一周学会Flask3 Python Web开发-客户端状态信息Cookie以及加密
前端·python·flask·flask3
化作繁星21 分钟前
如何在 React 中测试高阶组件?
前端·javascript·react.js
Au_ust28 分钟前
千峰React:函数组件使用(2)
前端·javascript·react.js
爱吃南瓜的北瓜39 分钟前
npm install 卡在“sill idealTree buildDeps“
前端·npm·node.js
TTc_42 分钟前
记录首次安装远古时代所需的运行环境成功npm install --save-dev node-sass
前端·npm·sass
翻滚吧键盘1 小时前
npm使用了代理,但是代理软件已经关闭导致创建失败
前端·npm·node.js
烂蜻蜓1 小时前
Uniapp 设计思路全分享
前端·css·vue.js·uni-app·html