JS中的防抖和节流问题

254 阅读4分钟

今天,我们来复习一下JS中的防抖和节流问题,防抖和节流是性能优化中的实际应用技巧,应用场景非常广泛,同时也是前端面试的经典考题,在此,我会将防抖和节流的概念、实现思路和实际手写代码一一给大家解释清楚,如有错误还请指正,谢谢。

首先我们要知道用户可能在某个时间多次触发一个事件,这个事件可能是鼠标点击,也可能是滑动窗口,如果此时我们不加限制,就会频繁触发回调函数,这时大量的计算无疑会严重影响网页性能,所以函数节流和函数防抖应运而生。

函数防抖

什么是函数防抖?

函数防抖指的是一个事件触发函数会在设定的时间后被执行,但是如果有新的事件,则需要重新计时。本质上说,函数防抖实现了延时功能,特殊点在于此延时是由最后一个用户事件决定的。 函数防抖的效果是,当频繁触发一个事件时,比如持续按下按键输入字符,回调函数不会立刻执行,而是等待用户停止按键输入n秒后再执行。

函数防抖的场景

  • 搜索联想,用户不停地输入字符时
  • window触发resize时

手写函数防抖

function debounce(fn, wait){
    var timer = null;
    //这里使用闭包,timer一直存在于内存中,第一次之后的调用都会直接访问闭包
    return function(){
        if(timer !== null){
            clearTimeout(timer);
        }
        timer = setTimeout(fn, wait);
    }
}

function handle(){
    console.log("this is decounce")
}

window.addEventListener('scroll', debounce(handle, 1000));

函数节流

什么是函数节流?

函数节流可以形象地理解为液压系统中的节流阀,这个函数也就是“阀”控制了回调函数执行的流量(次数)。它标准的定义是在规定的时间内,节流函数只允许执行一次,当在某个时间内多次触发该函数,则从第一次触发开始计时,到达规定时间后再执行函数。总结一下,函数节流是由第一次触发决定的,第一次触发开始计时,限定时间内后面的触发都会被忽略。

节流应用场景

  • 鼠标不断的点击
  • 监听滚动事件

手写函数节流

函数节流有两种方式,一个是通过定时器,另一个是使用时间戳。定时器方式和防抖类似,通过闭包保存上一次定时器的状态,如果定时器为null,则设置新的定时器,到时间则执行回调函数,并将定时器置为null。定时器的特点是延时执行,所以第一次触发事件后要等到定时器规定的时间后才会执行一次,当最后一次停止触发,还会执行一次回调。时间戳版也是通过闭包保存上一次的时间戳,然后和触发事件的当前时间戳对比,如果大于规定时间则执行回调,否则什么都不做。它的特点是第一次触发会执行,之后要等规定时间后才能再次执行回调函数。

// time stamp
var throttle = function(func, delay){
    // previous time when bind to the event
    var prev = Date.now();
    // actual events happened are in the enclosure 
    return function(){
        var context = this;
        var args = arguments;
        // current time
        var now = Date.now();
        if (now - prev >= delay){
            // operation
            func.apply(context, args);
            prev = Date.now();
        }
    }
}


function handle(){
    console.log("this is throttle");
}

window.addEventListener('scroll', throttle(handle,1000));
// timer version
var throttle2 = function(func,delay){
    // define a empty timer
    var timer = null;
    // actual events happened are in the enclosure 
    return function(){
        var context = this;
        var args = arguments;
        // if the timer is null, set the timer
        if(!timer) {
            timer = setTimeout(function(){
                // operation
                func.apply(context, args);
                // once we have finished the operation, clear the timer
                timer = null;
            }, delay);
        }
    }
}

function handle2(){
    console.log("this is throttle");
}

window.addEventListener('scroll',throttle2(handle2,1000));

下面这个版本是时间戳+定时器合体版,即在delay时间内,可以生成新的定时器,但是只要delay时间到了,就不会再生成定时器,而是直接执行回调。这个方案非常完美,解决了防抖太有耐心的问题,使得页面迟迟没有响应的问题得到解决。

// Advanced version: time stamp + timer
var throttle3 = function(func,delay){
    // define timer and initial time stamp
    var timer = null;
    var startTime = Date.now();
    // use closure
    return function(){
        // get current time when the event happened
        var curTime = Date.now();
        var remaining = delay - (curTime - startTime);
        var context = this;
        var args = arguments;
        clearTimeout(timer);
        if(remaining <= 0){
            func.apply(context,args);
            startTime = Date.now();
        }else{
            timer = setTimeout(func, remaining);
        }
    }
}

function handle3(){
    console.log("this is advanced throttle");
}

window.addEventListener('scroll',throttle3(handle3,1000));