性能优化之:debounce & throttle

为什么要用?

如果事件处理函数无限制调用,会大大加重浏览器的工作量,有可能导致页面卡顿影响体验;后台接口的频繁调用,不仅会影响客户端体验,还会大大增加服务器的负担。

让某个函数在一定 事件间隔条件(去抖debounce) 或 时间间隔条件(节流throttle) 下才会去执行,避免快速多次执行函数(操作DOM,加载资源等等)给内存带来大量的消耗从而一定程度上降低性能问题。

针对这个问题,一般有两个方案: 防抖 (Debounce) 节流 (Throttle)

防抖(Debounce)

原理

在事件被触发n秒后,再去执行回调函数。如果n秒内该事件被重新触发,则重新计时。结果就是将频繁触发的事件合并为一次,且在最后执行。

例如

电梯5秒后会关门开始运作,如果有人进来,等待5秒,5秒之内又有人进来,5秒等待重新计时…直至超过5秒,电梯才开始运作。

使用场景

scroll事件(资源的加载)
mousemove事件(拖拽)
resize事件(响应式布局样式)
keyup事件(输入框文字停止打字后才进行校验)

实现

每当事件触发,就去重置定时器。直至最后一次事件被触发,n秒后再去执行回调函数。

先做基本的准备(篇幅原因,HTML部分省略):

let container = document.getElementById('container');

// 事件处理函数
function handle(e) {
    console.log(Math.random()); 
}

// 添加滚动事件
container.addEventListener('scroll', handle);

我们发现,每滚动一下,控制台就会打印出一行随机数。

基础防抖

我们现在写一个最基础的防抖处理:

function debounce(func, wait) {
    var timeout;//标记
    return function() {
      clearTimeout(timeout);
      timeout = setTimeout(func, wait);
    }
}

事件也做如下改写:

container.addEventListener('scroll', debounce(handle, 1000));

现在试一下, 我们会发现只有我们停止滚动1秒钟的时候,控制台才会打印出一行随机数。

标准防抖

以上基础版本会有两个问题,请看如下代码:

// 处理函数
function handle(e) {
console.log(this); //输出Window对象
console.log(e); //undefined
}

没错,当我们不使用防抖处理时,handle()函数的this指向调用此函数的container,而在外层使用防抖处理后,this的指向会变成Window。
其次,我们也要获取到事件对象event。
所以我们要对防抖函数做以下改写:

function debounce(fn, wait) {
  let timeout;
  return function() {
    let that = this;
    let arg = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(function(){
      fn.apply(that,arg)//使用apply改变this指向
    }, wait);
  }
}

复制代码当然了,如果使用箭头函数便可以省去外层声明。

function debounce (fn, delay) {
                return args => {
                    clearTimeout(fn.id)
                    fn.id = setTimeout(() => {
                        fn.call(this, args)
                    }, delay)
                }
            }

先触发式防抖

以上的情况都是只有当连续触发停止后才执行,那如果我们想让事件第一次触发就执行,后面的连续触发都不执行,直到停止触发一段时间才可以再次触发(比如防止频繁点击),该如何处理呢?
那么可以利用同样的原理,稍作修改即可:

function debounce(fn, wait) {
    let timeout;
    return function(){
      let arg = arguments;
      let that = this;
      clearTimeout(timeout);
      !timeout && fn.apply(that,arg)
        timeout = setTimeout(function(){
          timeout = null;
        }, wait);
    }
}

节流 (Throttle)

原理

规定一个时间n,n秒内,将触发的事件合并为一次并执行。

例如

电梯等第一个人进来之后,5秒后准时运作,不等待,若5秒内还有人进来,也不重置。

使用场景

  • click事件(不停快速点击按钮,减少触发频次)
  • scroll事件(返回顶部按钮出现\隐藏事件触发,监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了防抖后,只有用户停止滚动后,才会判断是否到了页面底部;如果是节流的话,只要页面滚动就会间隔一段时间判断一次。)
  • keyup事件(输入框文字与显示栏内容复制同步 搜索联想)
    减少发送ajax请求,降低请求频率
  • DOM 元素的拖拽功能实现(mousemove);
  • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹);

标准节流

function throttle(fn, wait) {
  let timeout; 
  return function () {
    if (!timeout) { 
      timeout = setTimeout(() => {
        timeout = null;
        fn.apply(this, arguments)
      }, wait)
    }
  }
}
function throttle (fn, delay) {
                return args => {
                    if (fn.id) return
                    fn.id = setTimeout(() => {
                        fn.call(this, args)
                        clearTimeout(fn.id)
                        fn.id = null
                    }, delay)
                }
            }

用滚动事件来描述节流,其实是一个非常典型的场景,比如需要用滚动事件判断是否加载更多等。

先触发式节流

和防抖函数类似,以上的情况是先等待后触发,如果我们想让事件先触发后等待,该如何处理呢?网上大部分文章都告诉你用时间戳的方式去实现,其实只要像防抖一样稍作修改即可实现。

function throttle(fn, wait) {
  let timeout; 
  return function () {
    if (!timeout) { 
      fn.apply(this, arguments)
      timeout = setTimeout(() => {
        timeout = null;
      }, wait)
    }
  }
}

复制代码这样,我们就会发现第一次触发函数就会立即生效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值