JS之防抖和节流

防抖和节流都是为了避免窗口的resize、scroll、输入框内容校验等事件处理函数被频繁调用,因此采用防抖和节流来限制事件调用的频率

防抖

原理介绍

防抖是指当持续触发事件时,一定时间段内没有再次触发事件,事件处理函数才会执行一次,如果设定的时间段内又一次触发了事件,就重新开始计时。

防抖在很多场景中都有应用:

  1. 输入框中频繁的输入 内容时,间隔一段时间来检验用户输入的内容
  2. 频繁点击按钮来触发某个事件时
  3. 监听浏览器的滚动事件
  4. 窗口的resize事件

举个例子,比如淘宝购物时询问客服,在交流过程中,客服会等待用户一分钟的时间(假设),如果在一分钟内,用户还会发消息(事件触发),那么客服就会回消息,然后继续等待一分钟,看看用户是否还会发消息;如果等待时间超过一分钟了,那么就断开和用户的连接(执行响应函数)

防抖.png

在上图中,事件触发并不是立即执行响应函数,而是等待一段时间后才会触发响应函数;而且当是事件触发多次时,等待时间会持续推迟到最后一个事件触发,然后等待一段时间后执行响应函数。因此都是以最后一次事件触发开始计时。

代码实现及优化

自定义实现

实现思路:

  1. 当触发一个函数时,不会立即执行该函数,而是等待一段时间后再执行(通过定时器来延迟函数的执行);
  2. 如果在等待时间段内,又重新触发函数,那就取消上一次的函数执行(取消定时器);
  3. 如果在延迟时间内,没有重新触发函数,那么这个函数将正常执行
//防抖函数的实现(fun为需要处理的函数,wait为延迟时间)
//默认情况下,fun函数的this是指向window的,但若fun函数需要用到返回函数中的arguments时,就需要改变this指针了
function debounce(fun, wait){
    let timer = null;
    return function(){
        if(timer) {clearTimeout(timer)};
        //获取this和arguments
        let context = this;
        let args = arguments;
        timer = setTimeout(function(){
            //获取对应节点对象的this和arguments
            fun.apply(context, args);
        },wait)
    }
}
//ES6新写法,箭头函数没有this,会向外查找this
function debounce(fun, wait){
    let timer = null;
    return ()=>{
        if(timer) {clearTimeout(timer)};
        timer = setTimeout(function(){
            //获取对应节点对象的this和arguments
            fun.apply(this, arguments);
        },wait)
    }
}

优化立即执行

若希望当第一次事件触发时就立即执行响应事件函数,而不是等待一段时间,后续事件触发时才等待。可以使用flag来标记是否是第一次执行,若为true,就按照第一次来执行,为false就按照防抖来执行

//优化,flag是用户上传来判断第一次是否立即执行
function debounce(fun,wait, flag){
    let timer = null;
    flag = flag || false;
    let handleFun = function(){
        if(timer){clearTimeout(timer);}
        let context = this;
        let args = arguments;
        if(flag){
            //设置一个变量来判断是否立即执行
            let isExe = false;
            if(!timer){
                fun.apply(context, args);
                isExe = true;
            }
            
            timer = setTimeout(function(){
                timer = null;
                if(!isExe){
                    fun.apply(context, args);
                }
            },wait);
        }else{
            timer = setTimeout(function(){
            	fun.apply(context, args);    
            }.wait)
        }
    }
    return handleFun;
}

优化返回值

若想让fun函数执行后有返回值,但是fun函数是发生在setTimeout函数中(异步执行的),所以通过return无法获取返回值

异步操作可以通过promise以及回调函数来获取返回值

//promise函数实现返回值
function debounce(fun, wait){
    let timer = null;
    return function(){
        return new Promise((resolve, reject)=>{
            if(timer) {clearTimeout(timer);}
            let context = this;
            let args = arguments;
            timer = setTimeout(function(){
                resolve(fun.apply(context, args));
            },wait);
        })
    }
}
//回调函数实现
function debounce(fun, wait, result){
    let timer = null;
    result = result || null; //通过result将结果回调出去
    return function(){
        if(timer){clearTimeout(timer);}
        let context = this;
        let args = arguments;
        timer = setTimeout(function(){
            let res = fun.apply(context, args);
            if(result){
                result(res);
            }
        },wait);
    }
}

节流

原理介绍

当持续触发事件时,保证一定时间内只调用一次事件处理函数。

比如飞机大战游戏,当我们按下空格键时会发射一颗子弹,但是当按下空格键频率很快时,子弹也会保持一定的频率来发射,而不是按一次就发射一颗子弹。按下空格的操作在一定时间段内触发了10次,但是响应函数就触发了一次

应用场景:

  1. 监听页面的滚动事件
  2. 鼠标移动事件
  3. 频繁点击按钮
  4. 游戏中的一些设计

节流.png

上图中,等待时间是固定的,不管在等待时间段内触发几次事件,响应函数只触发一次

代码实现及优化

自定义实现

实现思路:采用时间戳的方式来实现

  1. 用previous来记录上一次执行的时间
  2. 每次准备执行前,获取当前的时间now
  3. 函数执行后,将now赋值给previous
function throttle(fun, wait){
    let previous = 0;
    return function(){
        let context = this;
        let args = arguments;
        let now = new Date().getTime();//获取时间戳
        if(now - previous > wait){
            fun.apply(context, args);
            previous = now;
        }
    }
}
//ES6新写法
function throttle(fun, wait){
    let previous = 0;
    return ()=>{
        let now = new Date().getTime();//获取时间戳
        if(now - previous > wait){
            fun.apply(this, arguments);
            previous = now;
        }
    }
}

优化最后执行

通过上图可以看出,默认情况下,节流函数的最后一次不会执行。若希望最后一次能够执行,可以这样实现:

//优化最后执行
function throttle(fun,wait){
    let previous = 0;
    let timer = null;
    return function(){
        let context = this;
        let args = arguments;
        let now = new Date().getTime();
        if(now - previous > wait){
            if(timer){
                clearTimeout(timer);
                timer = null;
            }
            fun.apply(context, args);
            previous = now;
        }else if(timer === null){ //只是最后一次
            timer = setTimeout(function(){
                timer = null;
                fun.apply(context,args);
            },wait)
        }
    }
}

两者区别

防抖是将几次操作合并为一次进行;节流是在一定时间内只执行一次函数。

总而言之,防抖只是在最后一次事件后才触发函数;节流是保证在规定时间内执行一次事件处理函数

参考链接: https://mp.weixin.qq.com/s/qyeRecCBBwa-Zf_V-KIRxA

猜你喜欢

转载自blog.csdn.net/qq_31947477/article/details/105954559