函数防抖与节流

函数防抖(debounce)

  • 应用场景
  1. 在用户停止输入后,才进行表单验证
  2. 搜索框连续输入关键字,只有当停止输入后,才进行http访问,否则ajax交互太频繁
  • 定义
    当触发后再次触发,会取消上一次触发的执行,直到最后一次触发后过去设定时间后才执行。
  • 实现思路
    当事件触发之后,必须等待设定的时间间隔之后,回调函数才会执行,假若在等待的时间内,事件又触发了则重新再等待设定的时间间隔,直到事件在设定的时间间隔内事件不被触发,那么最后一次触发事件后,则执行函数。
    第一次调用函数,创建一个定时器,将需要执行的目标函数放到定时器里,在指定的时间间隔之后执行目标函数。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。目的是只有在执行函数的请求停止了一段时间之后才执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

/**
* 实现函数的防抖
* @param func {function} 需要执行的函数
* @param delay {number} 检测防抖的间隔频率,单位是毫秒(ms)
* @return {function} 可被调用执行的函数
*/

function debounce(func, delay = 1000, immediate = false) {
// 通过闭包缓存一个定时器 id
let timer = null;
// 返回一个函数,触发事件回调时执行这个返回函数
return function (...args) {
// immediate 为 true 表示第一次触发后执行
// timer 为空表示首次触发
if (!timer && immediate) {
func.apply(this, args);
}

// 如果已经设定过定时器,就清空上一次的定时器
if (timer) clearTimeout(timer);

// 开始设定一个新的定时器,定时器结束后执行传入的函数 func
// 这里直接使用箭头函数就不用保存执行上下文的引用了.
// 否则,需要let context = this保存函数作用域
// 再在定时器的执行函数中: func.apply(context,args)
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}

使用:
eg. 监听滚动事件

1
2
3
4
5
6
7

document.addEventListener("scroll", debounce(betterFn, 2000, true));

function betterFn() {
console.log('执行了!');
}

函数节流(throttle)

  • 应用场景
  1. 避免用户多次点击按钮时,重复提交表单
  2. 监控浏览器窗口大小(resize)
  • 定义
    当事件持续触发时,函数在一定时间间隔内(例如 n 秒)只执行一次,在这 n 秒内 无视后来产生的函数调用请求,也不会延长时间间隔。
  • 实现思路
  1. 时间戳
    在闭包函数的外部存储了一个previous变量(初始值为0)记录上次执行的时间戳,每次触发内部闭包函数时与上次的时间戳进行对比判断,如果间隔时间大于我们设置的等待时间,则执行函数,同时更新时间戳。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 时间戳版
    function throttle(fn, wait) {
    var context;
    // 上一次执行 fn 的时间
    var previous = 0;
    return function (...args) {
    // 获取当前时间,转换成时间戳,单位毫秒
    var now = +new Date();
    context = this;
    // 将当前时间和上一次执行函数的时间进行对比
    // 大于等待时间就把 previous 设置为当前时间并执行函数 fn
    if (now - previous > wait) {
    fn.apply(context, args);
    previous = now;
    }
    }
    }
  1. 定时器
    触发事件时,设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
    两个版本的节流函数最大的不同就是:函数执行的时间点,定时器版本由于setTimeout延时的特性,是在时间段结束时执行目标函数,而时间戳版本是在时间段开始时执行。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 定时器版
    function throttle(fn, wait) {
    let timer;
    return function (...args) {
    let context = this;
    if (!timer) {
    timer = setTimeout(function() {
    // clearTimeout(timeout) 并不会把 timeout 设为 null
    // 手动设置,便于后续判断
    timer = null;
    // apply() 的第二个参数需要的是类数组,故不需要...args
    fn.apply(context, args)
    }, wait)
    }
    }
    }

和防抖函数每次清除timer不同,节流函数对timer进行非空判断,只有它为空的时候才能设置定时器,这样保证了在一段时间内同时只有一个定时器。

debounce 和 throttle 区别

节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时

查看评论