首页 > 其他分享 >js面试(节流)

js面试(节流)

时间:2024-03-17 19:46:24浏览次数:23  
标签:触发 throttle 节流 timer js 面试 fun const

一、节流

在JavaScript中,节流(throttle)是一种常用的性能优化技术,用于限制某个函数在一定时间内的执行频率。具体来说,节流函数允许你在一段时间内只执行一次回调函数,即使在这段时间内触发了多次事件。这有助于防止因为频繁触发事件而导致的性能问题。

节流的实现原理是,在事件被触发后,一个定时器会被设置。如果在定时器完成之前,相同的事件再次被触发,那么这次触发不做任何处理,直到定时器任务完成后,清空定时器,才能对以后新的触发事件做出响应。这样,在一定的时间间隔内,只会对触发事件做一次处理。

应用场景:

  1. 滚动事件处理:在网页或应用中,滚动事件可能会非常频繁地触发,如果不加以控制,可能会导致性能问题。通过使用节流,可以限制滚动事件处理函数的执行频率,提高页面的响应速度和流畅度。
  2. 网络请求:对于需要连续发送请求的场景,如滚动加载数据或自动完成搜索,过多的请求不仅会增加服务器压力,还可能导致用户体验下降。通过节流,可以减少请求的次数,降低服务器压力,同时保证数据的及时加载。
  3. 动画效果:在动画过程中,如果过度频繁地刷新和渲染,可能会造成闪烁和卡顿现象。通过节流,可以控制动画的渲染频率,提高动画的流畅性和用户体验。

二、前置准备

  1. 准备一个html文件和一个throttle.js文件,throttle.js文件用来编写节流函数

    <!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>
      <div class="throttle">触发节流事件</div>
      <script src="./throttle.js"></script>
    </body>
    
    </html>
    
    // throttle.js
    const throttle = () => {};
    
  2. 给div绑定点击事件

    <!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>
      <div class="throttle">触发节流事件</div>
      <script src="./throttle.js"></script>
      <script>
        const clickEvent = function (e) {
          console.log("点击事件触发", e, this)
        }
        document.querySelector(".throttle").addEventListener("click", clickEvent)
      </script>
    </body>
    
    </html>
    

    image-20240317143334764

  3. 将clickEvent方法传递给throttle进行处理

    <!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>
      <div class="throttle">触发节流事件</div>
      <script src="./throttle.js"></script>
      <script>
        const clickEvent = function (e) {
          console.log("点击事件触发", e, this)
          console.log("执行时间", new Date().getSeconds())
        }
        const throttleClickEvent = throttle(clickEvent, 2000)
        document.querySelector(".throttle").addEventListener("click", throttleClickEvent)
      </script>
    </body>
    
    </html>
    
    // throttle.js
    const throttle = (fun, time) => {
      return fun;
    };
    

    在上面修改了一下clickEvent,用来打印时间。目前的效果和一开始没什么区别,下面就开始写节流函数

三、基础节流实现

  1. 第一次触发事件,设置一个定时器,只要这个定时器存在,就不再对触发事件做出响应。直到定时器任务完成,清空定时器

    // throttle.js
    const throttle = (fun, time) => {
      let timer;
      return function () {
        if (!timer) {
          timer = setTimeout(() => {
            fun();
            timer && clearTimeout(timer);
            timer = null;
          }, time);
        }
      };
    };
    

    image-20240317145108032

    现在频繁触发点击事件,但会间隔2秒触发一次事件处理函数。所以基本实现了节流

  2. 修改this指向

    // throttle.js
    const throttle = (fun, time) => {
      let timer;
      return function () {
        if (!timer) {
          timer = setTimeout(() => {
            fun.apply(this);
            timer && clearTimeout(timer);
            timer = null;
          }, time);
        }
      };
    };
    

    image-20240317145325447

  3. 获取参数

    // throttle.js
    const throttle = (fun, time) => {
      let timer;
      return function (...args) {
        if (!timer) {
          timer = setTimeout(() => {
            fun.apply(this, args);
            timer && clearTimeout(timer);
            timer = null;
          }, time);
        }
      };
    };
    

    image-20240317145434824

    上面就实现了一个最基本的节流函数

四、立即执行

// throttle.js
const throttle = (fun, time, immediately = false) => {
  let timer;
  return function (...args) {
    if (!timer) {
      // 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
      if (immediately) {
        fun.apply(this, args);
        timer = setTimeout(() => {
          timer && clearTimeout(timer);
          timer = null;
        }, time);
      } else {
        timer = setTimeout(() => {
          fun.apply(this, args);
          timer && clearTimeout(timer);
          timer = null;
        }, time);
      }
    }
  };
};

image-20240317150604902

五、尾部执行控制

  1. 首先我们修改一下html文件,方便测试

    <!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>
      <div class="throttle">触发节流事件</div>
      <input type="text" class="throttle-input">
      <script src="./throttle.js"></script>
      <script>
        const clickEvent = function (e) {
          console.log("点击事件触发", e, this)
          console.log("执行时间", new Date().getSeconds())
        }
    
        const inputEvent = function (e) {
          console.log("表单数据", e, this)
          console.log("执行时间", new Date().getSeconds())
        }
        const throttleClickEvent = throttle(clickEvent, 2000, true)
        document.querySelector(".throttle").addEventListener("click", throttleClickEvent)
        document.querySelector(".throttle-input").addEventListener("input", inputEvent)
      </script>
    </body>
    
    </html>
    

    image-20240317154851639

    目前没有做节流处理,每次输入都会触发

  2. 使用节流函数控制触发频率

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>节流</title>
      <style>
        input {
          width: 100%;
        }
      </style>
    </head>
    
    <body>
      <div class="throttle">触发节流事件</div>
      <input type="text" class="throttle-input">
      <script src="./throttle.js"></script>
      <script>
        const clickEvent = function (e) {
          console.log("点击事件触发", e, this)
          console.log("执行时间", new Date().getSeconds())
        }
    
        const inputEvent = function (e) {
          console.log("表单数据", this.value)
          console.log("执行时间", new Date().getSeconds())
        }
        const throttleClickEvent = throttle(clickEvent, 2000, true)
        const throttleInputEvent = throttle(inputEvent, 2000, true)
        document.querySelector(".throttle").addEventListener("click", throttleClickEvent)
        document.querySelector(".throttle-input").addEventListener("input", throttleInputEvent)
      </script>
    </body>
    
    </html>
    

    image-20240317155234084

    此时,第一次被触发了,然后两秒后,触发了第二次。但是最后一次触发事件却没有得到响应

  3. 修改节流函数

    // throttle.js
    const throttle = (fun, time, immediately = false, tail = false) => {
      let timer;
      let tailTimer;
      return function (...args) {
        if (!timer) {
          // 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
          if (immediately) {
            fun.apply(this, args);
            timer = setTimeout(() => {
              timer && clearTimeout(timer);
              timer = null;
            }, time);
          } else {
            timer = setTimeout(() => {
              fun.apply(this, args);
              timer && clearTimeout(timer);
              timer = null;
            }, time);
          }
        } else if (tail) {
          // 如果当前已经触发了响应事件,并且需要对最后一次触发做出回应,则将后面触发调用的处理函数加入定时器
          // 每一次有新的触发事件,则将上一次的定时器给清除,保证始终是最新的触发事件被加入
          tailTimer && clearTimeout(tailTimer);
          tailTimer = setTimeout(() => {
            fun.apply(this, args);
          }, time);
        }
      };
    };
    

    image-20240317155654229

    此时最后一次就会触发了。我们再来试一试不立即执行的情况

    const throttleInputEvent = throttle(inputEvent, 2000, false)
    

    image-20240317155933638

    通过控制台发现,最后一次输入的数字被打印了两次。为什么?

    image-20240317191622241

    首先,最后打印的肯定是放在tailTimer里面的方法,它在53秒执行,是在51秒的时候触发的。最后一次执行节流函数是52秒,它是在50秒的时候触发的。之所以这两个方法打印了同样的值,是因为最后一次触发点击事件是在51秒,两个定时器里面的方法,都是在51秒后执行的,等它们执行的时候,都会拿到最后那个最新的值。

    那立即执行为什么没有这个问题呢?因为立即执行打印的是当时获取到值。简单点说:就是延迟执行,在52秒和53秒都会打印51秒最后一次触发时候的值。立即执行的话,50秒的时候会最后一次使用timer,此时,直接打印出50秒时候的值,在接下来的2秒之内,timer不会再执行,在这2秒内,触发的最后一次事件会放进tailTimer里面。

    所以,对于延迟执行的情况,其实没有必要将最后一次加入到tailTimer,因为timer会获取到最后一次的值。

    // throttle.js
    const throttle = (fun, time, immediately = false, tail = true) => {
      let timer;
      let tailTimer;
      return function (...args) {
        if (!timer) {
          // 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
          if (immediately) {
            fun.apply(this, args);
            timer = setTimeout(() => {
              timer && clearTimeout(timer);
              timer = null;
            }, time);
          } else {
            timer = setTimeout(() => {
              fun.apply(this, args);
              timer && clearTimeout(timer);
              timer = null;
            }, time);
          }
        } else if (tail && immediately) {
          // console.log("尾部定时器当前记录参数:", args[0].srcElement.value);
          // 如果当前已经触发了响应事件,并且需要对最后一次触发做出回应,则将后面触发调用的处理函数加入定时器
          // 每一次有新的触发事件,则将上一次的定时器给清除,保证始终是最新的触发事件被加入
          tailTimer && clearTimeout(tailTimer);
          tailTimer = setTimeout(() => {
            fun.apply(this, args);
          }, time);
        }
      };
    };
    

    image-20240317192941966

    ​ 这下打印结果就没问题了。

    再来测试一下立即执行的情况

    const throttleInputEvent = throttle(inputEvent, 2000, true)
    

    image-20240317193141582

    立即执行的情况,就会通过tailTimer来打印最后的结果了。

六、完整代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>节流</title>
  <style>
    input {
      width: 100%;
    }
  </style>
</head>

<body>
  <div class="throttle">触发节流事件</div>
  <input type="text" class="throttle-input">
  <script src="./throttle.js"></script>
  <script>
    const clickEvent = function (e) {
      console.log("点击事件触发", e, this)
      console.log("执行时间", new Date().getSeconds())
    }

    const inputEvent = function (e) {
      const value = this.value
      console.log("表单数据", value)
      console.log("执行时间", new Date().getSeconds())
    }
    const throttleClickEvent = throttle(clickEvent, 2000, true)
    const throttleInputEvent = throttle(inputEvent, 2000, true)
    document.querySelector(".throttle").addEventListener("click", throttleClickEvent)
    document.querySelector(".throttle-input").addEventListener("input", throttleInputEvent)
  </script>
</body>

</html>
// throttle.js
const throttle = (fun, time, immediately = false, tail = true) => {
  let timer;
  let tailTimer;
  return function (...args) {
    if (!timer) {
      // 如果是立即执行,则直接执行处理函数,然后设置定时器,一段时间后才可以触发
      if (immediately) {
        fun.apply(this, args);
        timer = setTimeout(() => {
          timer && clearTimeout(timer);
          timer = null;
        }, time);
      } else {
        timer = setTimeout(() => {
          fun.apply(this, args);
          timer && clearTimeout(timer);
          timer = null;
        }, time);
      }
    } else if (tail && immediately) {
      // console.log("尾部定时器当前记录参数:", args[0].srcElement.value);
      // 如果当前已经触发了响应事件,并且需要对最后一次触发做出回应,则将后面触发调用的处理函数加入定时器
      // 每一次有新的触发事件,则将上一次的定时器给清除,保证始终是最新的触发事件被加入
      tailTimer && clearTimeout(tailTimer);
      tailTimer = setTimeout(() => {
        fun.apply(this, args);
      }, time);
    }
  };
};

更多的优化,比如:取消、获取返回值可以参考防抖的处理。

标签:触发,throttle,节流,timer,js,面试,fun,const
From: https://www.cnblogs.com/finish/p/18079015

相关文章

  • Vue.js前端开发零基础教学(一)
    目录第一章 初识Vue.js前言 开发的好处一.前端技术的发展什么是单页Web应用?二.Vue的简介三.Vue的特性四.Vue的版本五.常见的包管理六.安装node环境第一章 初识Vue.js学习目标:了解前端技术的发展了解什么是Vue掌握使用方法掌握Node.js环境的搭建前言......
  • js实现交通灯(两种方案)
    简介刚在抖音上刷到一个面试题,说实现交通灯的方案,我一开始想到的是通过定时器去实现,没想到他提到了一个问询的方式去实现,借此记录下来,本文介绍了两种方案去实现交通灯以及对应的倒计时。废话不多说,上代码html+csshtml代码如下:<!DOCTYPEhtml><htmllang="en"><head><......
  • 视野修炼-技术周刊第77期 | JSR 的愿景
    欢迎来到第77期的【视野修炼-技术周刊】,下面是本期的精选内容简介......
  • 力扣大厂热门面试算法题 39-41
    39.组合总和,40.组合总和II,41.缺失的第一个正数,每题做详细思路梳理,配套Python&Java双语代码,2024.03.17 可通过leetcode所有测试用例。目录39.组合总和解题思路完整代码PythonJava40.组合总和II解题思路完整代码PythonJava41.缺失的第一个正数解题思路完......
  • [nodejs] NodeJs/NPM入门教程
    0序nodejs是运行在服务器端的js,常用于前端工程师在本地电脑、或生产环境部署调试或运行前端工程。回想起来,上次使用nodejs,还在5年前做大学毕业设计时,基于前后端分离的实践(那时,业界正在兴起前后端分离的浪潮。当然了,现在的web工程,前后端分离已是默认的技术选择了)这次重......
  • 【Java面试题-基础知识03】Java线程连环问
    1、Java中的线程是什么?在Java中,线程是程序执行流的最小单元。每个Java程序都至少有一个主线程,也称为主执行线程,它是程序开始执行时自动创建的。除了主线程外,程序员还可以创建额外的线程来执行并发任务。2、创建线程的方式有哪些?Java中的线程由java.lang.Thread类表示,可以通过两......
  • 说JS作用域,就不得不说说自执行函数
    一个兜兜转转,从“北深”回到三线城市的小码农,热爱生活,热爱技术,在这里和大家分享一个技术人员的点点滴滴。欢迎大家关注我的微信公众号:果冻想前言不得不吐槽,学个JS,这个概念也太多了,但是这些概念你不懂吧,代码你都看不懂,你都寸步难行。好吧,这又遇到了作用域方面的知识盲区,然后发......
  • js面试(防抖)
    一、什么是防抖防抖(Debounce)是一种用于减少特定事件触发频率的技术。在编程中,它通常用于确保函数或方法不会在很短的时间内被频繁调用,这有助于优化性能并避免不必要的计算或操作。防抖的实现原理是,在事件被触发后,一个定时器会被设置。如果在定时器完成之前,相同的事件再次被触发,......
  • 全栈的自我修养 ———— js如何处理并发的大量请求??
    假如一个事件段内传过来一百多个请求,我们该处理大量并发请求呢过程实现验证假如现在允许的并发量为4,有一百个请求!假如现在允许的并发量为1,有一百个请求!源码过程实现定义一个方法,这个方法会放入你在这个时间段接收到的请求,这里100位例!consturlArr=[]......
  • Java面试题:假设你正在开发一个Java后端服务,该服务需要处理高并发的用户请求,并且对内存
    Java内存优化、线程安全与并发框架:综合面试题解析Java作为一种广泛使用的编程语言,其内存管理、多线程和并发处理是开发者必须掌握的核心技能。为了全面评估候选人在这些领域的知识水平和实际应用能力,我们设计了一道综合性的面试题。本文将对这道题目进行深入分析,从核心知识......