6.浏览器-节流防抖

244 阅读3分钟
/**
* 为什么需要防抖和节流:
*  在一些高频率事件触发的场景下我们不希望对应的事件处理函数多次执行
* 场景:
*  滚动事件
*  输入的模糊匹配
*  轮播图切换
*  点击操作
*  ....
* 浏览器默认情况下都会有自己的监听事件间隔( 4~6ms),如果检测到多次事件的监听执行,那么就
* 会造成不必要的资源浪费
*
* 前置的场景: 界面上有一个按钮,我们可以连续多次点击
*
* 防抖:对于这个高频的操作来说,我们只希望识别一次点击,可以人为是第一次或者是最后一次
* 节流:对于高频操作,我们可以自己来设置频率,让本来会执行很多次的事件触发,按着我们定
* 义的频率减少触发的次数
*
*/

防止事件在短时间内多次触发的而产生的两种解决方案

  • 防抖:就是一直点按钮,那就不会触发事件,只要停下来,就可以,

  • 节流:就是定时器,到时候触发,像游戏里面的技能冷却

防抖【只执行最后一次】

防抖是将多次操作合并为一次完成,

原理:维护一个计时器,在规定时间后触发函数,但是在该规定时间内再次触发的话,就会取消之前的定时器而重新设置,从而保证了只有最后一次操作能够被触发

// 防抖
function debounce(fn, wait, immediate){
    let timer = null;
    return function (...args){
        // 立即执行的功能,timer为空表示首次触发
        if(immediate && !timer){
            fn.apply(this,args)
        }

        // 有新的触发,则把定时器清空,就是有timer的时候就清空settimeout,这样下面的settimeout就一直进不来,内部函数使用了外部函数的变量timer,就是闭包
        timer && clearTimeout(timmer)
        
        // 重新计时 最后一次会执行settimeout,
        timer = setTimeout(()=>{
            // 这里的this是调用的对象input对象,
            fn.apply(this,args)
        },wait)
    }
}

let input =  debounce(function(){
    // 这里的this是window,window.value肯定是undefined,所以要在调用的时候改变this指向
    console.log(this.value)
},500,true)
  • 1.利用闭包保存一个timer变量,然后返回一个函数(这个返回的函数就是后续频繁触发操作中调用的函数)

  • 2.根据标志位判断是否第一次需要立即执行(因为有些情况是需要首次调用函数立即执行的,若没有该参数,就会在定时器到了之后才会执行)

  • 3.当有新的触发时,若存在定时器,则清空该定时器;

  • 4.设定一个新的定时器,重新计时

节流【控制执行次数】

节流函数的实现方式有两种,定时器、时间戳

定时器

// 节流定时器版本
function throttle(fn,wait){
    let timer = null;
    return function(...args){
        if(timer){
            timer = setTimeout(()=>{
                fn.apply(this,args)
                timer = null
            }, wait)
        }
    }
}

定时器版本的节流函数其重点是利用闭包保存timer变量,具有两个特点:

  • n秒后才会执行第一次(定时器到了时间后才会触发);

  • 停止触发后节流函数还会再执行一次(因为函数是延迟执行的,当停止触发时候其任务已经到了队列中,所以停止后还会执行一次)

时间戳

// 时间戳版本
function throttle(fn,wait){
    let previous = 0;
    return function(...args){
        let now = + new Data();
        if(now-previous>wait){
            previous = now;
            fn.apply(this,args)
        }
    }
}

时间戳版本的节流函数重点是利用闭包保存上一次的时机previous,具有两个特点:

  • 开发触发后立即执行(因为previous开始会被赋上0)
  • 停止触发后不再执行(因为该函数是同步任务,在触发的时候就会进行相应的判断,所以就不存在停止触发后再执行的情况)
// 晓舟throttle,原理都是类似的,还是得理解
function throttle(fn,delay){
    let flag = true
    return function(){
        if(flag){
            setTimeout(()=>{
                fn.call(this)
                flag = true
            },delay)
        }
        flag = false
    }
}