[JS]带你手写防抖与节流

494 阅读3分钟

最近想重新系统地整理一下前端的知识,因此写了这个专栏。我会尽量写一些业务相关的小技巧和前端知识中的重点内容,核心思想。

前言

防抖和节流是前端很基本的功能,在很多的工具库里都会有。在某些中小公司会用手写防抖,节流作为面试题。目的是考察开发者开发团队公共方法库的能力。今天我们就来尝试一下设计防抖与节流这2个功能的函数。

防抖 debounce

指在一个规定的时间范围内,只执行一次动作。换句话说,当用户规定频繁触发某个行为的时候,我们会在只执行一次该动作,其余的触发会被拦截。一般这个触发时间可以定在规定时间段的头或者尾。

手写防抖

函数参数有个3:

  • func: function,必传,要执行的回调函数
  • wait: number,选传,防抖时间间隔
  • immediate:boolean,选传,是否立即执行(执行时刻是在时间段头还是尾)

思路:

  • 先判断参数是否正确,没传的设置默认值
  • 设置定时器标识timer,通过闭包返回函数(这样函数可以访问timer)
  • 每次执行时,先判断当前timer是否空闲且immediate为true,说明这次需要执行函数
  • 清空timer
  • 重置timer
  • 如果这次需要执行函数,就直接执行,并返回结果
  • 如果函数在尾部执行就等定时器到期之后执行,并清空itmer。
const debounce = function debounce(func, wait, immediate) {
  	// 判断非法参数
    if (typeof func !== "function") throw new TypeError('func 必须为可执行函数');
  	// 如果传入参数忽略wait,func(func,immediate)。把wait的位置内容赋给immediate
    if (typeof wait === "boolean") immediate = wait;
  	// 如果没有wait就设置默认值
    if (typeof wait !== "number") wait = 500;
  	// 如果没有immediate(func(func,wait)或者类型不对就设置默认值
    if (typeof immediate !== "boolean") immediate = false;
  	// 设置时间标识
    let timer = null;
  	
    return function operate(...params) {
      	// 立即执行的条件:1.timer空闲 2. immediate 为true
        let now = !timer && immediate;
        let result;
        // 清空timer
        timer = clearTimer(timer);
      	// 重置timer
        timer = setTimeout(() => {
            // 定时器到了之后 清空timer
            timer = clearTimer(timer);
            // 时间段尾部执行
            if (!immediate) func.call(this, ...params);
        }, wait);
        // 时间段头部执行
        if (now) result = func.call(this, ...params);
        return result;
    };
};

节流 throttle

指在固定时间段里,设置一个频率,如果用户在这个时间段里以高于我们设定的频率执行函数。我们就强制要求函数以我们规定的频率执行。

仔细想想,其实所谓的控制执行频率就是限制每2次函数执行之间的时间间隔。

手写节流

函数参数有2个:

  • func: function,必传,要执行的回调函数
  • wait: number,选传,函数执行的时间间隔。

思路:

  • 先判断参数是否正确,没传的设置默认值
  • 设置定时器标识timer,通过闭包返回函数(这样函数可以访问timer)
  • 声明previous记录上次执行时间
  • 如果这次需要执行函数,就直接执行,并返回结果
const throttle = function throttle(func, wait) {
  	// 判断非法参数
    if (typeof func !== "function") throw new TypeError('func must be an function!');
  	// 如果没有wait就设置默认值
    if (typeof wait !== "number") wait = 500;
  	// 设置时间标识
    let timer = null
    // 上次执行时间
    let previous = 0;
    return function operate(...params) {
      	// 当前时间
        let now = +new Date();
        // 距离上次执行的时间差
        let remaining = wait - (now - previous);
        let result;
      	
        if (remaining <= 0) {
            // 两次间隔时间超过500ms了,让方法立即执行
          	// 清空定时
            timer = clearTimer(timer);
          	// 立即执行函数
            result = func.call(this, ...params);
          	// 记录本次执行时间
            previous = +new Date();
        } else if (!timer) {
            // 没设置过定时器等待,则我们设置一个去等待即可
            timer = setTimeout(() => {
              	// 清空定时
                timer = clearTimer(timer);
              	// 执行函数
                func.call(this, ...params);
               	// 记录本次执行时间
                previous = +new Date();
            }, remaining);
        }
      	// 返回执行结果
        return result;
    };
};

总结

防抖和节流不是很难的知识点,但手写防抖节流除了可以考察开发者对这2个概念的掌握以外,还能看出他在编写公共工具函数时的基本功(如入参判断,定时器标识处理等)。希望对大家有所帮助。