js中的防抖(debounce)与节流(throttle)函数以及源码解析和常用应用场景

1.节流函数

听名字我们也大概知晓什么意思,意思是创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔 wait毫秒调用一次该函数

听了上面的解析,这里就有一个问题,规定时间内执行,到底是++最开始就执行一次++,间隔wait秒在执行,还是说++wait秒中之后执行++,再wait秒中之后执行,网上看了很多博客,都没有提到这一点,其实这两种应用的是不一样的场景,下面会说到。

1.1在函数调用前先执行

思路如下:

利用闭包,创建一个变量,用来保存上次执行的时间,然后在函数中判断,如果时间间隔大于wait秒,执行函数,否则不作为

/**
 * @param func 执行函数
 * @param wait 时间间隔
 * @returns {Function}
 */
 let throttle = function (func, delay) {
            let prev = 0;
            return function () {
                let context = this;
                let args = arguments;
                let now = Date.now();
                if (now - prev >= delay) {
                    func.apply(context, args);
                    prev = Date.now();
                }
            }
        }

适用场景:防止用户重复提交,如表单提交,发送短信,输入次数过多等

1.2 在时间间隔之后执行方法

思路如下:

使用定时器,如果定时器不存在,则创建定时器,定时器中执行方法,同时清除定时器id

let throttle = function (func, delay) {
            let timer = null;
            return function () {
                let context = this;
                let args = arguments;
                if (!timer) {
                    timer = setTimeout(function () {
                        func.apply(context, args);
                        timer = null;
                    }, delay);
                }
            }
        }

适用场景:滚动条事件,搜索框输入发送ajax,输入框判断是否合法等

1.3让函数前时间间隔前后都执行

采用时间戳加定时器的形式

  let throttle = function (func, delay) {
            let timer = null;
            let startTime = Date.now();  //设置开始时间
            return function () {
                let curTime = Date.now();
                let remaining = delay - (curTime - startTime);  //剩余时间
                let context = this;
                let args = arguments;
                if (remaining <= 0) {      // 间隔大于或者等于delay秒
                    func.apply(context, args);
                    startTime = Date.now();
                } else {  // 间隔小于或者等于delay秒
                    clearTimeout(timer);  
                    timer = setTimeout(func, remaining);   //取消当前计数器并计算新的remaining
                }
            }
        }

remaining为两次方法执行间隔与delay的差距,为负数则表示,间隔时间大于delay秒,反之小于delay秒

1.4 扩展,结合上面三个方法,动态使用

使用第三个参数options,以对象形式,可以传入两个参数leading,trailing

/**
 * 创建并返回一个像节流阀一样的函数,当重复调用函数的时候,最多每隔 wait毫秒调用一次该函数
 * @param func 执行函数
 * @param wait 时间间隔
 * @param options 如果你想禁用第一次首先执行的话,传递{leading: false},
 *                如果你想禁用最后一次执行的话,传递{trailing: false}
 * @returns {Function}
 */
let throttle = function (func, wait, options) {
    let context, args, result;  //this上下文,参数,返回值
    let timeout = null;  //定时器id
    let previous = 0;   //上次执行的时间
    if (!options) options = {}; //禁用首次或者末次执行

    let later = function () { //remaining秒之后执行方法
        //初始化函数
        previous = options.leading === false ? 0 : new Date().getTime(); //没有禁用第一次执行,则previous=0
        timeout = null;  //初始化定时器id
        result = func.apply(context, args); //执行方法
        context = args = null;
    };
    return function () {
        let now = new Date().getTime();   //获取当前时间

        if (!previous && options.leading === false) {
            //当第一次执行时,如果禁用第一次执行(options.leading),将本次执行时间赋值给上次执行
            previous = now
        }

        let remaining = wait - (now - previous);   //用时间间隔wait减去(本次运行的时间now-上次运行的时间previous),使时间间隔为wait
        context = this;   //获取当前环境this
        args = arguments; //获取函数参数

        if (remaining <= 0 || remaining > wait) { //如果两次方法执行时间间隔刚好为wait,或者大于wait,
            previous = now;  //将本次时间赋值给上次
            result = func.apply(context, args);   //执行方法
            context = args = null
            
        } else if (!timeout && options.trailing !== false) {
            // 时间间隔小于wait,且定时器不存在,且最后一次执行
            timeout = setTimeout(later, remaining);  //在remaining时间后执行方法,remaining为距离wait所剩余的时间
        }
        return result;
    };
}

大致原理就是根据变量初始化previous变量,和是否生成定时器

2.防抖函数

当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

这个和节流不同的地方是,这个函数如果一直触发,则一直不会执行,只会在不触发的wait秒后执行一次

在函数停止执行的wait秒后执行

  let debounce = function (fn, wait) {
            let timeout = null;
            return function () {
                if (timeout !== null) clearTimeout(timeout);
                timeout = setTimeout(fn, wait);
            }
        }

原理很简单,使用定时器,每次运行时判断定时器是否存在,存在则清除定时器,在生成一个wait秒后执行的定时器

扩展,自定义函数首次执行还是末次执行

和上面一样,传入第三个参数,控制执行

/**
 * 防反跳。func函数在最后一次调用时刻的wait毫秒之后执行!
 * @param func 执行函数
 * @param wait 时间间隔
  * @param options 如果你想第一次首先执行的话,传递{leading: true},
 *                如果你想最后一次执行的话,传递{trailing: true}
 * @returns {Function}
 */

let debounce = function (func, wait, options) {
    let timeout, args, context, timestamp, result;
    let later = function () {
        let last = new Date().getTime() - timestamp; // timestamp会实时更新
        if (last < wait && last >= 0) {  //如果定时器执行时间距离上次函数调用时间,大于0,小于wait,则重新生成定时器,时间间隔为距离wait所剩余的时间
            timeout = setTimeout(later, wait - last);
        } else {
            timeout = null;  //清除定时器
            if (options.trailing) {  //如果定义时间间隔后执行
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            }
        }
    };
    return function () {
        context = this;   //绑定函数上下文
        args = arguments;
        timestamp = new Date().getTime();  //当前执行方法时间
        let callNow = options.leading && !timeout;
        if (!timeout) {
            //生成定时器
            timeout = setTimeout(later, wait);
        }
        if (callNow) { //如果定义首次执行
            //立即执行
            result = func.apply(context, args);
            context = args = null;
        }
        return result;
    };
}

原理,执行时判断定时器,如果定时器存在,则不作为,然后在定时器执行方法中判断时间间隔,大于0小于wait,则生成新的定时器,每次执行方法动态更新timestamp的值。

本文首发于 https://zhtblog.cn/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值