JavaScript 前端性能优化之事件防抖

607 阅读2分钟

定义: 指触发事件后在规定时间内回调函数只能执行一次,如果在规定时间内又触发了该事件,则会重新开始算规定时,以新的事件的时间为准,n 秒后才执行

应用场景:

  • 输入款搜索时,用户在不断输入值时,用防抖来节约请求资源。
  • 按钮点击:比如点赞,收藏

防抖有两种实现方式:

  • 非立即执行:大概步骤函数调用->延迟->执行回调函数,如果在延迟的时候又触发了函数,则重新进行延迟操作,延迟结束后执行回调函数
  • 立即执行:大概步骤函数调用->执行回调函数->延迟,如果在延迟的时候又触发了函数,则重新进行延迟操作,延迟结束后不会执行回调函数

非立即执行

function debounce(fun,wait=300){
  let timer = null;
    return function () {
        let self = this,
            args = arguments;
        timer && clearTimeout(timer);
        timer = setTimeout(function () {
            method.apply(self,args);
        },delay);
    }
}

立即执行

 function debounce(func,wait,immediate){
            let timeout;
            return function(){
                let context = this;
                let args = arguments;

                if(timeout){
                    clearTimeout(timeout);
                }
                if(immediate){
                    // 如果已经执行过,不再执行
                    timeout = setTimeout(function(){
                        timeout = null;
                    },wait);
                    if (!timeout) func.apply(context, args)
                }else{
                    timeout = setTimeout(function(){
                        func.apply(context,args);
                    },wait);
                }
            }
        }

扩展

假设我们传入的func是有返回值,所以我们也要返回函数的执行结果,但是当 immediate 为 false 的时候,因为使用了 setTimeout ,我们将 func.apply(context, args) 的返回值赋给变量,最后再 return 的时候,值将会一直是 undefined,所以我们只在 immediate 为 true 的时候返回函数的执行结果。

 function debounce(func,wait,immediate){
            let timeout,result;
            return function(){
                let context = this;
                let args = arguments;

                if(timeout){
                    clearTimeout(timeout);
                }
                if(immediate){
                    // 如果已经执行过,不再执行
                    timeout = setTimeout(function(){
                        timeout = null;
                    },wait);
                    if (!timeout) result = func.apply(context, args);
                }else{
                    timeout = setTimeout(function(){
                        func.apply(context,args);
                    },wait);
                }
                return result;
            }
        }

最后我们再思考一个小需求,我希望能取消 debounce 函数,比如说我 debounce 的时间间隔是 10 秒钟,immediate 为 true,这样的话,我只有等 8 秒或者是更长时间后才能重新触发事件,现在我希望有一个按钮,点击后,取消防抖,这样我再去触发,就可以又立刻执行啦

 function debounce(func,wait,immediate){
            let timeout,result;
            let debounced = function(){
                let context = this;
                let args = arguments;

                if(timeout){
                    clearTimeout(timeout);
                }
                if(immediate){
                    // 如果已经执行过,不再执行
                    timeout = setTimeout(function(){
                        timeout = null;
                    },wait);
                    if (!timeout) result = func.apply(context, args);
                }else{
                    timeout = setTimeout(function(){
                        func.apply(context,args);
                    },wait);
                }
                return result;
            }
            
           debounced.cancel = function(){
               clearTimeout(timeout);
                timeout = null;
           } 
        }