js 的防抖与节流

64 阅读3分钟

一、概念

防抖(debounce)

防止抖动,当你持续触发事件时,一定时间段内没有再次触发事件,事件处理函数才会执行一次,以此往后顺延说人话就是:你就一直抖吧!我就静静地看着你抖,等你不抖了我再执行

防抖_图.png

节流(throttle)

节约流量,当你持续处罚事件时,保证一定时间段内只调用一次事件处理函数,固定时间段固定时间间隔处理函数说人话就是:你就一直在那触发吧!我有我自己的节奏,我每隔一定时间段执行一次

节流_图.png

二、分别适用什么场景

防抖:input (一直输入,只要最后一次不再输入才会执行)节流:resize、scroll (一定要执行的,但避免执行频率过高)

三、退后,我要开始手写了

退退退.gif

面试官问 1:怎么写一个防抖函数

function debounce(fn, delay) {
  let timer = 0
  return function() {
    // 如果这个函数已经被触发了,就清除掉重新生成
    if(timer){
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments); // 透传 this 和参数
      timer = 0
    }, delay)
  }
} 

搞定

面试官问 2:怎么手写一个节流函数

1. 时间戳写法

function throttle(fn, delay) {
    let last = 0; // 上次执行的时间
    return function(){
        let now = Date.now(); // 当前时间
        if(now - last >= delay) {
            last = now;
            fn.apply(this, arguments)
        }
    }
}
function handle(){
    console.log('被执行了!')
}

const throttleHandle = throttle(handle, 1000);
throttleHandle();
throttleHandle();
throttleHandle();
throttleHandle();

心想短短十几行代码搞定,稳了稳了
这时面试官问道:你这个写法第一次触发会立即执行吗,最后一次触发会执行吗?
我一看,果然第一次是立即执行,最后一次也不一定会执行
面试官接着问:怎么写一个不立即执行的节流函数?
被逼无奈,重新写

2. 定时器写法

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

当你长舒一口气,以为终于要过了的时候
这时候面试官又问了:这种写法是不是最后一次触发后也要 delay 延迟啊?万一用户点击最后一次后立即退出了页面怎么办?

面试官说的有理有据啊,没办法,继续重写

3. 时间戳定时器写法结合起来

function throttle(fn, delay){
    let timer = null;
    let startTime = Date.now();
    return function(){
        let curTime = Date.now(); // 当前时间,用来计算剩余时间(从上次执行完到现在还有多久执行下次函数)
        let remainning = delay - (curTime - startTime);// curTime - startTime 本次间隔已经过去多久
        let context = this;
        let args = arguments;
        clearTimeout(timer);
        if(remainning <= 0){ // 说明应该立即执行下一次事件
            fn.apply(context, args);
            startTime = Date.now();
        } else { // 距离下次还剩余时间,重新生成 timer,延迟时间改为 remainning
            timer = setTimeout(fn, remainning);
        }
    }
}

这回妥了,喜滋滋

4. 节流函数总结

  • 时间戳写法:缺点是第一次会立即执行,最后一次不一定会执行
  • 定时器写法:缺点是最后一次会延迟执行
  • 时间戳 + 定时器写法:完美解决