fe-hunter icon indicating copy to clipboard operation
fe-hunter copied to clipboard

实现防抖和节流

Open BetaSu opened this issue 2 years ago • 8 comments

要实现的功能

请按照以下顺序回答该问题:

1.什么是防抖和节流? 2.应用场景有哪些? 3.手写防抖和节流,简单版本即可。

代码示例

function debounce() {
  // ...
}

function throttling() {
  // ...
}

最佳答案评选标准

  1. 请写出实现思路、代码,并为代码的关键步骤增写注释
  2. 附带测试用例是加分项
  3. 不需要考虑异常发生
  4. 实现简单版本的基础上,能提出可以优化的扩展点是加分项

最佳答案

悬赏中,欢迎作答...

答题同学须知

  • 答题规范:请在一次评论中完成作答,后续修改也请编辑该评论,而不是追加新的评论

  • 评选标准:最佳答案由围观同学的 👍 和卡颂共同决定

  • 评选时间:一般是问题发布24小时后评选,如果问题发布当天回答数较少,问题悬赏金额可能增加,同时悬赏时间也会增加

围观同学须知

  • 对于你满意的答案,请不要吝惜你的 👍,这是评选最佳答案的依据

  • 非答题的评论会被删除,问题相关讨论请在赏金猎人群中进行

BetaSu avatar Apr 14 '22 02:04 BetaSu

防抖

/**
 * 函数防抖:短时间内多次触发同一事件,只执行第一次或者最后一次,中间的不执行
 */
/**
 * @param {function} func 事件响应函数
 * @param {number} delay 延迟时间
 * @param {boolean} imme 是否立即执行
 */
function debounce(func, delay, imme) {
  var timer

  return function () {
    var _self = this,
      _args = arguments,
      res

    if (timer) clearTimeout(timer)

    if (imme) {
      /**
       * 触发事件后立即执行,指定时间内不再执行,若指定时间内再次触发则重置定时
       * 例如,点击按钮进行提交
       */
      if (!timer) res = func.apply(_self, _args)

      timer = setTimeout(function () {
        timer = null
      }, delay)
    } else {
      /**
       * 触发事件指定时间后执行,若指定时间内再次触发则重置定时
       * 例如,输入框内容判断
       */
      timer = setTimeout(function () {
        res = func.apply(_self, _args)
      }, delay)
    }

    return res
  }
}

节流

/**
 * 函数节流:一段时间内多次触发同一事件,但只按指定时间间隔执行
 * 例如,图片懒加载
 */
/**
 * @param {function} func 事件响应函数
 * @param {number} delay 间隔时间
 */
/**
 * 通过计算时间间隔
 * 问题:最后一次触发不会执行
 */
function throttle(func, delay) {
  // var begin = new Date().getTime()
  var begin = 0

  return function () {
    var _self = this,
      args = arguments,
      res,
      now = new Date().getTime()

    if (now - begin > delay) {
      res = func.apply(_self, args)
      begin = now
    }

    return res
  }
}

/**
 * 通过定时器
 * 问题:第一次触发不会立即执行
 */
function throttle(func, delay) {
  var timer

  return function () {
    var _self = this,
      _args = arguments,
      res

    if (!timer) {
      timer = setTimeout(function () {
        timer = null
        res = func.apply(_self, _args)
      }, delay)
    }

    return res
  }
}

/**
 * 最终版:通过定时器 + 计算时间间隔
 */
function throttle(func, delay) {
  var begin = 0,
    timer

  return function () {
    var _self = this,
      _args = arguments,
      res,
      now = new Date().getTime()

    if (now - begin >= delay) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }

      begin = now
      res = func.apply(_self, _args)
    } else if (!timer) {
      timer = setTimeout(function () {
        timer = null
        res = func.apply(_self, _args)
      }, delay)
    }

    return res
  }
}

Stan9726 avatar Apr 14 '22 02:04 Stan9726

防抖

防抖原理

维护一个计时器,在规定的delay时间后触发函数,在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。

应用场景

  • 验证文本输入。搜索框输入、手机号、邮箱验证输入检测等
  • 窗口大小resize。只需窗口调整完之后,计算窗口大小,防止重复渲染。
  • 监视滚动(scroll)时间(在添加去抖动后滚动,只有在用户停止滚动后才会确定它是否已到达页面底部)。

实现代码

function fn(...arg) {
  console.log (arg[0]);  //输出events对象
}
//防抖-频繁触发,只有最后一次会生效
function debounce(fn, delay=500){
    //定时器初始化
    let timer = null
    //通过闭包的方式,来获取定时器,同时返回onclick调用的函数
    return function(){
      timer && clearTimeout(timer)
      timer = setTimeout(()=>{
        //这里的arguments为外层的arguments(包含了event事件对象),箭头函数没有自己的arguments
        fn(arguments)
      }, delay)
    }
  }
document.getElementById('btn').onclick = debounce(fn, 1000)

节流

节流原理

函数执行一次后,只有在大于设置的执行周期后才会执行第二次。持续触发事件时,保证一定时间段内只调用一次事件处理函数。

应用场景

  • 实现DOM元素的拖放功能mousemove
  • 计算鼠标移动距离

节流实现

function throttle(fn, delay){
  //时间初始化
  let oldTime = 0
  return function(){
    lastTime = Date.now()
    if((lastTime - oldTime)> delay){
      fn(arguments)
      //记录触发的时间作为oldTime
      oldTime = lastTime
    }
  }
}
document.onscroll = throttle(fn, 1000)

两者区别

节流旨在时间段内控制触发的频率,防抖则是旨在时间段内只触发最后一次。

QiuShuiDyp avatar Apr 14 '22 02:04 QiuShuiDyp

防抖

export function debounce<Args extends any[], F extends (...arg: Args) => any>(
  fn: F,
  wait: number = 500,
  immediate: boolean = false
) {
  let timer: ReturnType<typeof setTimeout> | null = null;
  return function (this: ThisParameterType<F>, ...args: Parameters<F>) {
    const context = this;
    if (timer) {
      clearTimeout(timer);
    }
    if (immediate) {
      if (!timer) {
        fn.apply(context, args);
      }
      timer = setTimeout(() => {
        timer = null;
      }, wait);
    } else {
      timer = setTimeout(() => {
        fn.apply(context, args);
      }, wait);
    }
  };
}

节流

export function throttle<Args extends any[], F extends (...args: Args) => any>(
  fn: F,
  wait: number = 500
) {
  let canRun = true;
  return function (this: ThisParameterType<F>, ...args: Args) {
    if (!canRun) {
      return;
    }
    const context = this;
    canRun = false;
    setTimeout(() => {
      fn.apply(context, args);
      canRun = true;
    }, wait);
  };
}

childrentime avatar Apr 18 '22 06:04 childrentime

防抖?节流?

首先我们说说防抖和节流的概念,防抖节流都是稀释执行频率的方式

  • 防抖:多次调用,只响应最后一个执行
  • 节流:单位时间内只调用一次

实现思路

  • 防抖: 设置一个单位时间的定时器,如果定时器未执行期间内触发,则重新定时
  • 节流:判断当前时间是否大于上次执行完的时间+ delay,如果大于再执行

应用场景

  • 防抖: 界面resize
  • 节流:输入补全

具体实现

  • 防抖
function debounce(fn, delay=500){
    let timer = null
    //通过闭包的方式,来获取定时器,同时返回onclick调用的函数
    return function(){
      timer && clearTimeout(timer)
      timer = setTimeout(()=>fn(), delay)
    }
  }
  • 节流
function throttling(fn, delay=500) {
  let last = 0;
  return (...args) => {
    const now =  Date.now();
    if (now > last + delay) {
      last = now;
      fn.apply(this, args);
    }
  };
}

lkzwc avatar Apr 20 '22 09:04 lkzwc

防抖

/**
 * **防抖**
 * 限制给定时间内连续触发的效果
 * @param {Function} fn 
 * @param {number} time 防抖时间间隔
 * @param {boolean} immediate 是否立即执行,默认false
 * @returns {Function}
 */
export const debounce = function (fn, time, immediate = false) {
      let timeOut = null;
      let startTime = 0;
      return function wrapThrottle(...args) {
            let delayTime = time;
            if (timeOut) window.clearTimeout(timeOut);
            if (immediate && !timeOut) delayTime = startTime + time < Date.now() ? 0 : time;
            const callback = () => {
                  fn.apply(this, args)
                  startTime = Date.now();
                  timeOut = null;
            }
            timeOut = window.setTimeout(callback, delayTime)
      }
}

使用场景:监听鼠标移动事件;

节流

/**
 * **节流**
 * 只能在限制的时间间隔触发
 * @param {Function} fn 
 * @param {number} time 
 * @param {boolean} immediate 第一次是否立即执行,默认true
 * @returns 
 */
export const throttling = function (fn, time, immediate = true) {
      let timeOut = null;
      let startTime = 0;
      return function wrapThrottle(...args) {
            let delayTime = time;
            if (timeOut) return;
            if (immediate) delayTime = startTime + time - Date.now();
            const callback = () => {
                  fn.apply(this, args)
                  startTime = Date.now();
                  timeOut = null;
            }
            timeOut = window.setTimeout(callback, delayTime)
      }
}

使用场景:向后台更新用户输入;

fightZy avatar Apr 27 '22 01:04 fightZy

什么是防抖和节流?

  • 防抖和节流都是为了规避一段时间连续触发多次事件或请求的需求策略
  • 防抖一般是取这段时间的最后一次(事件和请求)执行
  • 节流一般是取这段时间的第一次(事件和请求)执行

应用场景有哪些?

  • 防抖一般用于搜索输入框,用户输入搜索关键字1234,原本通过change事件会执行4次事件请求,参数分别是(1,12,123,1234),通过防抖只会取最后一次1234
  • 节流一般用于页面渲染(又要很快渲染又要很少重绘和重排),比如初始化加载的时候会因为参数的变化执行多次,但是很多时候参数并没有变化,同样的参数让页面渲染了很多次,于是需要对比参数,后面的参数如果和现在的是一样的就不会渲染(react会有一个useDeepCompareEffect钩子,用的人很少,深比对参数节流,diff算法也有类似模块)

手写防抖和节流,简单版本即可。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>防抖节流</title>
</head>
<body>
  <button onclick="ClickHandle(debounce, 2000)">防抖</button>
  <button onclick="ClickHandle(throttle, 2000)">节流</button>
  <script>
    let timer = null
    let timer1 = null
    let count = 0
    function ClickHandle(fn, delay){
      fn(function(t){
        console.log(t)
      }, delay)()
    }

    function debounce(fn, delay){
      count += 1
      console.log('执行防抖函数'+count+'次', delay)
      return function(){
        timer && clearTimeout(timer)
        timer = setTimeout(()=>{
          count = 0
          fn('执行程序')
        }, delay)
      }
    }
    function throttle(fn, delay){
      count += 1
      return function(){
        let now = Date.now()
        if((now - timer1)> delay){
          fn('节流-2s执行一次')
          timer1 = now
        }
      }
    }
  </script>
</body>
</html>

buildlove avatar May 12 '22 03:05 buildlove

防抖

防抖,顾名思义,防止抖动,以免把一次事件误认为多次,敲键盘就是一个每天都会接触到的防抖操作。

场景

  1. 登录、发短信等按钮避免用户点击太快,以致于发送了多次请求,需要防抖
  2. 调整浏览器窗口大小时,resize次数过于频繁,造成计算过多,此时需要一次到位,就用到了防抖
  3. 文本编辑器实时保存,当无任何更改操作一秒后进行保存

防抖重在清零 clearTimeout(timer) 

function debounce (f, wait) {

  let timer

  return (...args) => {

    clearTimeout(timer)

    timer = setTimeout(() => {

      f(...args)

    }, wait)

  }

}

节流

节流,顾名思义,控制水的流量。控制事件发生的频率,如控制为1s发生一次,甚至1分钟发生一次。与服务端(server)及网关(gateway)控制的限流 (Rate Limit) 类似。

场景

  1. scroll 事件,每隔一秒计算一次位置信息等
  2. 浏览器播放事件,每个一秒计算一次进度信息等
  3. input 框实时搜索并发送请求展示下拉列表,没隔一秒发送一次请求 (也可做防抖)

节流重在开关锁 timer=null 

function throttle (f, wait) {

  let timer

  return (...args) => {

    if (timer) { return }

    timer = setTimeout(() => {

      f(...args)

      timer = null

    }, wait)

  }

}

总结

  • 防抖:高频率触发的事件,在指定的单位时间内,只响应最后一次,如果在指定时间再次触发,则重新计算时间。 代码实现重在清零 clearTimeout
  • 节流:控制流量,单位时间内事件只能触发一次,如果服务器端的限流即 Rate Limit。代码实现重在开锁关锁 timer=timeout; timer=null

zhangbinzhbb avatar Jul 13 '22 02:07 zhangbinzhbb

这是来自QQ邮箱的假期自动回复邮件。   您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。

imondo avatar Jul 13 '22 02:07 imondo