前端面试题:你了解防抖(debounce)与节流(throttle)吗?

1,249 阅读4分钟

什么是防抖函数

概念: 

函数防抖(debounce)是指在一定时间内,在动作被连续频繁触发的情况下,动作只会被执行一次,也就是说当调用动作过n毫秒后,才会执行该动作

原理

  1. debounce 函数在主线程顺序执行时已经被调用,传入的参数一个是真正想在事件触发执行的事件处理函数
  2. 另一个参数是事件触发的间隔时间,间隔时间内再次触发事件,则重新计时,类似于罚你 5 分钟内不准说话,时间到后就可以开始说话,如果 5 分钟内说话了,则再罚你 5 分钟内不准说话,以此类推~

应用场景

在用户和前端页面的交互过程中,很多操作的触发频率非常高,比如鼠标移动 mousemove 事件, 滚动条滑动 scroll 事件, 输入框 input 事件, 键盘 keyup 事件,浏览器窗口 resize 事件。


function debounce(fn, delay) {
    // 参数为传入的事件处理函数和间隔时间
    var interval = delay || 1000;
    // 闭包保存的 timer 变量,会常驻内存
    var timer = null;
    //返回的匿名函数是事件的回调函数,在事件触发时执行,参数为 DOM 事件对象(event)
    return function() {
        var args= arguments;
        // 事件的回调函数中,this 指向事件的绑定的 DOM 元素对象(HTMLElement)
        var context = this;
        //如果事件回调函数中存在定时器,则清空上次定时器,重新计时。如果间隔时间到后,处理函数自然就被执行了。
        clearTimeout(timer);
        //定时器时间到后,执行事件真正的处理函数 handler
        timer = setTimeout(function() {
            //执行的事件处理函数(handler),需要把调用对象 this 和事件对象 传递过去,就像没被debounce处理过一样
            fn.apply(context, args);
        }, interval);
    };
}

实例

var inputEl = document.getElementById("nwdInput");
// debouce 函数执行了,返回一个函数,该函数为事件的回调函数
inputEl.oninput = debounce(ajax);
// 事件真正的处理函数(handler),参数是回调函数传递过来的。
// 常见场景就是边输入查询关键字,边请求查询数据,比如百度的首页搜索
function ajax(event) {
    console.log("HTTP 异步请求:", event.target.value);
}




什么是节流函数

概念

节流函数(throttle)就是让事件处理函数(handler)在大于等于执行周期时才能执行,周期之内不执行,即函数节流是间隔时间执行,不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数。

原理

节流函数可以用时间戳和定时器两种方式进行处理。当我触发一个事件时,先setTimout让这个事件延迟一会再执行,如果在这个时间间隔内又触发了事件,那我们就clear掉原来的定时器,再setTimeout一个新的定时器延迟一会执行

应用场景

  1. 页面滚动和改变大小时需要进行业务处理,比如判断是否滑到底部,然后进行懒加载数据。 
  2. 按钮被高频率地点击时,比如游戏和抢购网站。

时间戳方式实现:

function throttle(fn, delay) {
    var delay = delay || 1000;
    //初始化一个时间,也作为高频率事件判断事件间隔的变量,通过闭包进行保存。
    var previous =  new Date().getTime();
    return function() {
        var context = this;
        var args = arguments;
        var now = new Date().getTime();
        //如果本次触发和上次触发的时间间隔超过设定的时间  就执行事件处理函数 (eventHandler)
        if (now - previous >= delay) {
            fn.apply(context, args);
            //然后将本次的触发时间,作为下次触发事件的参考时间。
            previous = now;
        }
    };
}

实例

var count = 0;
window.onload = function() {
    window.onscroll = throttle(eventHandler, 1000);
};

function eventHandler(e) {
    var containerEl = document.getElementById("container");
    containerEl.innerHTML = count;
    count++;
}


定时器方式实现

function throttle(fn, delay) {
    var delay = delay || 1000;
    var timer = null;
    return function() {
        var args = arguments;
        var context = this;
        if (!timer) {
            timer = setTimeout(function() {
                clearTimeout(timer);
                timer = null;
                fn.apply(context, args);
            }, delay);
        }
    };
}

两种方式对比

对比时间戳和定时器两种方式,效果上的区别主要在于: 事件戳方式会立即执行,定时器会在事件触发后延迟执行,而且事件停止触发后还会再延迟执行一次。 具体选择哪种方式取决于使用场景