函数的防抖和节流详解

2,347 阅读3分钟

在前端开发的过程中经常会遇到这样的需求--通过鼠标的事件(mousemove,srcoll 等)连续触发目标函数,如果不与后台交互,在某些情况下还说的过去,但是一旦与后台交互,比如Ajax请求,那么这个时候会对后台造成很大的压力,那么如何才能避免这种状况呢?这里就不得不请出今天要说的主角--函数的防抖和节流。

函数防抖与函数节流的区别:

函数节流: 不是让函数不执行,而是让函数在一定的时间内执行的次数减少一些

函数防抖: 是超过一定的时间内执行一次,比如在mousemove事件中,鼠标停止移动后的1s后才执行。 如果还不明白,可以移步的这里自己体验一把吧,哈哈!函数防抖和节流体验

函数防抖

先看一个例子,效果图(从参考文章上扒下来的,说明问题就好)

var num = 1;
var content = document.querySelector('.container');
addNum ();

function addPlus() {
    num++;
    content.innerHTML = num
}

document.onmousemove = addNum

函数防抖前的样子,可以想象如果addPlus里面执行的不是简单的计算,而是向服务器发送请求,可以预见服务器要承担多大的压力。

如果使用防抖函数呢? 且看如下函数和效果图

document.onmousemove = debounce(addPlus,1000)

// 一般的防抖函数
function debounce(doSomething,wait){
    var timeout;//需要一个外部变量,为增强封装,所以使用闭包
    return function(){
        var _this = this,
            _arguments = arguments;
            // 关键,每次执行前都要先清空上一个timeOut,由于速度特别快,
            // 所以在移动鼠标的时候,里面的函数不会执行,直到当鼠标停止的时候,此时生成最后一个定时器
            // 并1秒后调用doSomething
        clearTimeout(timeout);
        // 再生成一个Timeout
        timeout = setTimeout(function(){
            doSomething.apply(_this,_arguments);
        },wait);
    }
};

立即执行版


function debounceIm(doSomething,wait,isImmediate){
    var timeout;
    return function(){
        var _this = this,
            _arguments = arguments;
        clearTimeout(timeout);
        if(isImmediate){
            var isTrigger = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            isTrigger&&doSomething.apply(_this,_arguments);
        }else{
            timeout = setTimeout(function(){
                doSomething.apply(_this,_arguments);
            },wait);
        }
    }
}
document.onmousemove = debounceIm(addPlus,1000,true);

上述函数解析:

如果isImmediate=true:

当鼠标第一次开始移动的时候,timeoutundefined,所以isTriggertrue,所以就会立即执行isTrigger&&doSomething.apply(_this,_arguments);并且timeout已经是一个对象了,此时如果鼠标不停的移动就会导致isTriggerfalse,延时器一直被清空,直到停下来的时候过wait后执行timeout=null

如果isImmediate=false

就不说啦,跟第一次总结的一样。

上述的效果图如下:

函数节流

截留的目的不是不执行,而是让函数执行的频率变低,通常有两种方案,一种是使用时间戳,一种是使用定时器

时间戳版


function trottle(doSomeThing,wait) {
    var _this,
        _arguments,
        initTime = 0;
    return function(){
        var now = +new Date();
            _this = this;
            _arguments = arguments;
            // 当间隔时间大于一定的时间后才触发对应的函数
        if(now - initTime>wait){
            doSomeThing.apply(_this,_arguments);
            initTime = now;
        }
    }
}
document.onmousemove = trottle(addPlus,2000)

第一次mousemove的时候,会立即执行一次(因为第一次mousemove的时候now - initTime很大),之后就看时间间隔了

定时器(主要思想是通过mousemove不停创建新的延时器)

function throttle(doSomething,wait){
    var timeout;
    return function(){
        var _this = this;
            _arguments = arguments;
        if(!timeout){
            timeout = setTimeout(function(){
                // 主要是让每次move的时候,使!timeout为true
                timeout = null;
                doSomething.apply(_this,_arguments);
            },wait);
        };
    }
}
document.onmousemove = throttle(addPlus,2000)

这个方法的执行过程是这样的:

第一次mousemove由于timeoutundefined,所以会进入判断并创建一个延时器,此时如果我们立即不断地mousemove!timefalse,就不会进入判断,直到间隔wait时间段后,timeoutnull,此后再mousemove就会创建新的延时器,以此类推不断地循环...

总结

这篇文章主要记录一下自己对参考文章的思考过程,其中可能会有疏漏,还望大家指正~

参考文章: