JS高级——节流函数

JS高级——节流函数在某个时间内 比如 500ms 某个函数只能被触发一次 过程 当事件触发时 相应的函数并不会立即触发 按照一定的频率去执行默认情况下 我们的防抖函数最后一次是不会执行的因为没有达到最终的时间

大家好,欢迎来到IT知识分享网。

1、什么是节流

  • 在某个时间内(比如500ms),某个函数只能被触发一次;

过程:

  • 当事件触发时,相应的函数按照一定的频率去执行

JS高级——节流函数

2、节流的应用场景

  • 监听页面的滚动事件;
  • 鼠标移动事件;
  • 用户频繁点击按钮操作;
  • 游戏中的一些设计;

总之,依然是密集的事件触发,但是这次密集事件触发的过程,不会等待最后一次才进行函数调用,而是会按照一定的频率进行调用;

3、节流函数的实现

下面我们根据需求场景,一步一步深入实现节流函数

 3.1 节流基本功能

节流函数的默认实现思路我们采用时间戳的方式来完成:

  • 我们使用一个last来记录上一次执行的时间
    • 每次准备执行前,获取一下当前的时间now:now - last > interval
    • 那么函数执行,并且将now赋值给last即可

代码实现 :

//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @returns */ function throttle(fn, interval) { // 1.记录上一次的开始时间 let lastTime = 0 // 2.事件触发时, 真正执行的函数 const _throttle = function () { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长时间需要去触发函数 const remainTime = interval - (nowTime - lastTime) //第一次会执行,原因是nowTime刚开始是一个很大的数字,结果为负数 //若最后一次没能满足条件,不会执行 if (remainTime <= 0) { // 2.3.真正触发函数 fn() // 2.4.保留上次触发的时间 lastTime = nowTime } } return _throttle }

代码调用:

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <input type="text">
  <button id="cancel">取消</button>
  <script src="./throttle.js"></script>
  <script>

    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector("#cancel")
    let counter = 0

    //输入触发事件
    const inputChange = function (event) {
      console.log(`发送了第${++counter}次网络请求`)
    }
    inputEl.oninput = throttle(inputChange, 1000)
  </script>
</body>

</html>
  3.2 绑定this和参数

  我们知道在oninput事件触发时会有参数传递,并且触发的函数中this是指向当前的元素节点的

  • 目前我们fn的执行是一个独立函数调用,它里面的this是window
    • 我们需要将其修改为对应的节点对象,而返回的function中的this指向的是节点对象;
  • 目前我们的fn在执行时是没有传递任何的参数的,它需要将触发事件时传递的参数传递给fn
    • 而我们返回的function中的arguments正是我们需要的参数;

所以我们的代码可以进行如下的优化:

//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @returns */ function throttle(fn, interval) { // 1.记录上一次的开始时间 let lastTime = 0 // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长时间需要去触发函数 const remainTime = interval - (nowTime - lastTime) //第一次会执行,原因是nowTime刚开始是一个很大的数字,结果为负数 //若最后一次没能满足条件,不会执行 if (remainTime <= 0) { // 2.3.真正触发函数 fn.apply(this, args) // 2.4.保留上次触发的时间 lastTime = nowTime } } return _throttle }

代码调用:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <input type="text">
  <button id="cancel">取消</button>
  <script src="./throttle.js"></script>
  <script>

    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector("#cancel")
    let counter = 0

    //输入触发事件
    const inputChange = function (event) {
      console.log(`发送了第${++counter}次网络请求`, this, event)
    }
    inputEl.oninput = throttle(inputChange, 1000)
  </script>
</body>

</html>
  3.3 节流函数第一次触发,优化立即执行

目前我们的函数触发第一次是都是立即执行的,但是某些场景是用户开始输入时的第一次是不立即执行的,我们可以如何优化呢?

  • 我们可以让用户多传入一个参数:leading
  • leading为true时,或者不传(默认为true),第一次触发会立即执行
  • leading为false时,第一次不会立即执行
  • 我们可以根据是否传入leading进行不同的处理方式:
//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true }) { // 1.记录上一次的开始时间 let lastTime = 0 const {leading} = options // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() //当lastTime为0时且第一次不执行,此时 remainTime = interval - (nowTime - lastTime) = interval > 0 if (!lastTime && !leading) lastTime = nowTime//决定第一次是否执行函数 // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime) if (remainTime <= 0) { // 2.3.真正触发函数 fn.apply(this, args) // 2.4.保留上次触发的时间 lastTime = nowTime } } return _throttle }

代码调用:

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <input type="text">
  <button id="cancel">取消</button>
  <script src="./throttle.js"></script>
  <script>

    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector("#cancel")
    let counter = 0

    //输入触发事件
    const inputChange = function (event) {
      console.log(`发送了第${++counter}次网络请求`, this, event)
    }
    inputEl.oninput = throttle(inputChange, 2000, { leading: false })
  </script>
</body>

</html>
3.4 优化节流函数最后一次执行

默认情况下,我们的防抖函数最后一次是不会执行的

  • 因为没有达到最终的时间,也就是条件now - last > interval满足不了的
  • 但是,如果我们希望它最后一次是可以执行的,那么我们可以让其传入对应的参数来控制

我们来看一下代码如何实现:

  • 我们增加了判断语句:
    • 当最后一次需要执行且没有定时器时,只需要创建一个定时器,并且还有多久触发由remainTime决定
  • 如果固定的频率中执行了回调函数
    • 因为需要执行回调函数,所以如果存在定时器则需要取消;
    • 并且将timer赋值为null,这样的话可以开启下一次定时器;
  • 如果定时器最后执行了,那么timer需要赋值为null
    • 因为下一次重新开启时,只有定时器为null,才能进行下一次的定时操作;
//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行, trailing 最后一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true, trailing: false,}) { const { leading, trailing } = options; // 1.记录上一次的开始时间 let lastTime = 0; let timer = null; // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime(); //当lastTime为0时且第一次不执行 if (!lastTime && !leading) lastTime = nowTime; // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime); if (remainTime <= 0) { if (timer) { //执行函数时,需要取消定时器 clearTimeout(timer); timer = null; } // 2.3.真正触发函数 fn.apply(this, args); // 2.4.保留上次触发的时间 lastTime = nowTime; return; } //当最后一次需要执行且没有定时器时,只需要创建一个定时器,还有多久触发由remainTime决定 if (trailing && !timer) { timer = setTimeout(() => { fn.apply(this, args); timer = null; //执行后重置 lastTime = !leading ? 0 : new Date().getTime(); //第一次不执行时为0,第一次执行时为定时器执行时的时间 }, remainTime); } }; return _throttle; } 

代码调用:

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <input type="text">
  <button id="cancel">取消</button>
  <script src="./throttle.js"></script>
  <script>

    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector("#cancel")
    let counter = 0

    //输入触发事件
    const inputChange = function (event) {
      console.log(`发送了第${++counter}次网络请求`, this, event)
    }
    inputEl.oninput = throttle(inputChange, 2000, { leading: true , trailing: true})
  </script>
</body>

</html>
3.5 当我们触发函数时,在未到执行时间,可以取消函数执行 

与防抖函数的取消功能是类似的: 

//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行, trailing 最后一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true,trailing: false}) { // 1.记录上一次的开始时间 const {leading,trailing} = options let lastTime = 0 let timer = null // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() if (!lastTime && !leading) lastTime = nowTime // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime) if (remainTime <= 0) { if (timer) { clearTimeout(timer) timer = null } // 2.3.真正触发函数 fn.apply(this, args) // 2.4.保留上次触发的时间 lastTime = nowTime return } if (trailing && !timer) { timer = setTimeout(() => { fn.apply(this, args) timer = null lastTime = !leading ? 0 : new Date().getTime() }, remainTime) } } _throttle.cancel = function () { if (timer) clearTimeout(timer) timer = null lastTime = 0 } return _throttle }

代码调用

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <input type="text">
  <button id="cancel">取消</button>
  <script src="./throttle.js"></script>
  <script>

    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector("#cancel")
    let counter = 0

    //输入触发事件
    const throttleChange = throttle(inputChange, 2000, { leading: false, trailing: true })
    inputEl.oninput = throttleChange

    // 取消功能
    cancelBtn.onclick = function () {
      throttleChange.cancel()
    }
  </script>
</body>

</html>
 3.6 当触发的函数有返回值时,获取在节流函数中执行的结果

与防抖函数类似,有两种方法:

  • 方法一:通过回调函数
  • 方法二:通过Promise的resolve

 (1)给throttle函数的options,多添加一个参数,参数为一个回调函数:

//throttle.js / * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行, trailing 最后一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true,trailing: false}) { // 1.记录上一次的开始时间 const { leading,trailing,resultCallback} = options let lastTime = 0 let timer = null // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() if (!lastTime && !leading) lastTime = nowTime // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime) if (remainTime <= 0) { if (timer) { clearTimeout(timer) timer = null } // 2.3.真正触发函数 const result = fn.apply(this, args) if (resultCallback && typeof resultCallback === 'function') resultCallback(result) //函数回调返回返回值 // 2.4.保留上次触发的时间 lastTime = nowTime return } if (trailing && !timer) { timer = setTimeout(() => { const result = fn.apply(this, args) if (resultCallback && typeof resultCallback === 'function') resultCallback(result) //函数回调返回返回值 timer = null lastTime = !leading ? 0 : new Date().getTime() }, remainTime) } } _throttle.cancel = function () { if (timer) clearTimeout(timer) timer = null lastTime = 0 } return _throttle }

代码调用:

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <input type="text">
  <button id="cancel">取消</button>
  <script src="./throttle.js"></script>
  <script>

    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector("#cancel")
    let counter = 0

    const inputChange = function (event) {
      console.log(`发送了第${++counter}次网络请求`, this, event)
      // 返回值
      return "aaaaaaaaaaaa"
    }

    // 方法一 通过添加一个函数参数来获取
    const throttleChange = throttle(inputChange, 1000,{
      leading: false,
      trailing: true,
      resultCallback: function (res) {
        console.log("resultCallback:", res)
      }
    })
    inputEl.oninput = throttleChange

    // 取消功能
    cancelBtn.onclick = function () {
      throttleChange.cancel()
    }
  </script>
</body>

</html>

 (2)使用Promise来返回执行结果:

/ * @param {*} fn 要执行的函数 * @param {*} interval 时间间隔 * @param {*} options 可选参数: leading第一次是否执行, trailing 最后一次是否执行 * @returns */ function throttle(fn, interval, options = {leading: true,trailing: false}) { // 1.记录上一次的开始时间 const { leading,trailing} = options let lastTime = 0 let timer = null // 2.事件触发时, 真正执行的函数 const _throttle = function (...args) { return new Promise((resolve, reject) => { //promise方式返回返回值 // 2.1.获取当前事件触发时的时间 const nowTime = new Date().getTime() if (!lastTime && !leading) lastTime = nowTime // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数 const remainTime = interval - (nowTime - lastTime) if (remainTime <= 0) { if (timer) { clearTimeout(timer) timer = null } // 2.3.真正触发函数 const result = fn.apply(this, args) resolve(result) // 2.4.保留上次触发的时间 lastTime = nowTime return } if (trailing && !timer) { timer = setTimeout(() => { const result = fn.apply(this, args) resolve(result) timer = null lastTime = !leading ? 0 : new Date().getTime() }, remainTime) } }) } _throttle.cancel = function () { if (timer) clearTimeout(timer) timer = null lastTime = 0 } return _throttle }

 代码调用:

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <input type="text">
  <button id="cancel">取消</button>
  <script src="./throttle.js"></script>
  <script>

    const inputEl = document.querySelector("input")
    const cancelBtn = document.querySelector("#cancel")
    let counter = 0

    const inputChange = function (event) {
      console.log(`发送了第${++counter}次网络请求`, this, event)
      // 返回值
      return "aaaaaaaaaaaa"
    }

    // 方法二 返回一个promise
    const throttleChange = throttle(inputChange, 3000, {
      leading: false,
      trailing: true
    })
    const tempCallback = function (...args) {
      throttleChange.apply(this, args).then(res => {//此时this绑定的是input对象
        console.log("Promise的返回值结果:", res)
      })
    }
    inputEl.oninput = tempCallback

    // 取消功能
    cancelBtn.onclick = function () {
      throttleChange.cancel()
    }
  </script>
</body>

</html>

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/118478.html

(0)
上一篇 2025-11-12 20:20
下一篇 2025-11-12 20:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信