JavaScript 防抖、节流

浏览器的 resizescrollkeypressmousemove 等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制,这就出现了防抖节流

防抖(debounce)

( 防抖 ):短时间内,多次触发同一个函数,只会执行最后一次
(在规定的延迟时间内,如果再次触发函数,则会清掉上一次函数,重新触发一个新函数,不停地触发则会不停地清除上一次函数,直到停止操作,然后延迟时间之后,该函数被执行,延迟时间一般较短)

// 普通方案
window.addEventListener('resize', () => {
  console.log('trigger');
})
// 会不停的触发resize函数,不停地输出trigger

在 resize 事件上绑定处理函数,这时 debounce 函数会立即调用,实际上绑定的函数时 debounce 函数内部返回的函数(这点很重要)
每一次事件被触发,都会清除当前的 timer 然后重新设置超时调用,只有在最后一次触发事件,才能在 delay 时间后执行(实现防抖的原理)

// debounce 函数接受一个函数和延迟执行的时间作为参数
function debounce(fn, delay){
    // 维护一个 timer
    let timer = null;
    
    return function() {
        // 获取函数的作用域和变量
        let context = this;
        let args = arguments;
        // clearTimeout是关键,在上次函数未被执行时,再次触发事件,会清除上次定时任务
        clearTimeout(timer);
        timer = setTimeout(function(){
            fn.apply(context, args);
        }, delay)
    }
}
function foo() {
  console.log('trigger');
}

// 在 debounce 中包装我们的函数,过 2 秒触发一次
window.addEventListener('resize', debounce(foo, 2000));

我们也可以为 debounce 函数加一个参数,可以选择是否立即执行函数

function debounce(func, delay, immediate){
    var timer = null;
    return function(){
        var context = this;
        var args = arguments;
        if(timer) clearTimeout(timer);
        if(immediate){
            var doNow = !timer;
            timer = setTimeout(function(){
                timer = null;
            },delay);
            if(doNow){
                func.apply(context,args);
            }
        }else{
            timer = setTimeout(function(){
                func.apply(context,args);
            },delay);
        }
    }
}

节流(throttle)

( 节流 ):规定的延迟时间内,只允许函数执行一次
(规定的延迟时间内,无论做多少次操作都只执行一次函数)
(与防抖的不同是,防抖如果你一直操作,如mousemove,一直不会执行回调函数,只有当停下时,才会执行)
(节流是,在规定的延迟时间内,如2秒,无论你操作多少次,都只会执行一次函数,如果一直操作的话,那么每2秒会执行一次,不会像防抖一样,只有停下来才会执行)
应用场景如:输入框的联想,可以限定用户在输入时,只在每两秒钟响应一次联想。

时间戳实现:可以看出,时间戳实现节流第一次触发事件时,结果会在第一时间输出

    // 节流
    function throttle(func, delay) {
      // 设置prev = 0为参考系
      var prev = 0;

      return function () {
        var context = this;
        var args = arguments;
        var now = Date.now();
        // 由于 now - 0 必大于 delay 所以,首次语句必定被执行
        if (now - prev >= delay) {
          func.apply(context, args);
          // 一次执行完之后,将prev = 当前时间,这一步是实现节流的关键,使得函数在规定延迟时间内只能执行一次
          prev = Date.now();
        }
      }
    }
    function sayHi(e) {
      console.log(e.target.innerWidth, e.target.innerHeight);
    }
    window.addEventListener('resize', throttle(sayHi, 300));

定时器实现:可以看出,定时器实现节流第一次触发事件时,结果不会被第一时间输出

    // 节流
    function throttle(func, delay) {
      // 初始化timer = false,可以令闭包函数中的定时器立马被执行
      // 但是这里要注意,只是定时器被立即执行,因为定时器是延时的,所以定时器里面的代码在delay后执行
      var timer = false;

      return function () {
        var context = this;
        var args = arguments;
        // !false就是true所以这里被立即执行了
        if (!timer) {
          // 这里是关键,将timer赋值成定时器,此时的timer为true
          // !true就是false,所以不走if条件里面的代码,直到定时器里面的代码执行完毕
          // timer被重新赋值为false
          timer = setTimeout(function () {
            func.apply(context, args);
            timer = false;
          }, delay);
        }
      }
    }
    function sayHi(e) {
      console.log(e.target.innerWidth, e.target.innerHeight);
    }
    window.addEventListener('resize', throttle(sayHi, 300));

时间戳定时器 实现节流 区别 在于:

使用 时间戳 实现的节流函数会在第一次触发事件时立即执行,以后每过 delay 秒之后才执行一次,并且最后一次触发事件不会被执行;

定时器 实现的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最后一次停止触发后,还会再执行一次函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值