详解防抖和节流

250 阅读2分钟

防抖和节流

一、 为什么需要防抖和节流

在编写业务的过程中,通常会给元素绑定很多事件。执行事件的过程中,如果存在“高频”触发的场景,多次且连续的触发,可能会造成不必要的性能问题,影响体验。从优化的角度触发,此时需要对事件进行需要进行防抖和节流。

如以下场景:

  • 点击按钮提交表单(多次点击)
  • 页面滚动
  • 输入框:输入模糊搜索
  • ...

二、什么是函数防抖

在事件触发的时候,开始计时,如果在规定时间内,事件再次被触发,则清空上一次计时,然后重新开始计时,保证在规定时间内事件没有再次被触发,再去执行这个事件。

简单的说就是在规定时间内多次被触发,仅仅只会执行一次(可以控制开始就执行,还是最后一次再执行)。如多次点击提交表单按钮,在一定时间内只执行一次

/**
 * 防抖
 * @param {fnc} function
 * @param {wait} number        延迟多久执行
 * @param {immediate} boolean  是否第一次触发就立即执行
 * @return {function}
 */
function debounce(func, wait, immediate) {
    if (typeof func !== 'function') throw new TypeError('func must be an function!')
    if (typeof wait === 'undefined') wait = 500
    if (typeof wait === 'boolean') {
        immediate = wait
        wait = 500
    }
    if (typeof immediate !== 'boolean') immediate = false

    // 设定定时器返回值标识
    let timer = null;
    return function proxy(...params) {
        let self = this
        
        // immediate为ture并且不存在定时器才算第一次执行
        let now = immediate && !timer

        clearTimeout(timer);
        timer = setTimeout(function () {
            timer = null;
            if (!immediate) {
                func.call(self, ...params)
            }
        }, wait);

        // 第一次触发就立即执行
        if (now) {
            func.call(self, ...params)
        }
    };
}

// 使用
function handle(e) {
    // 在点击的时候要处理的业务
    console.log(this, e)
}

document.getElementById('submit').addEventListener('click', debounce(handle, true))

三、什么是函数节流

在某一次高频触发下,不是只识别一次,而是按照设定的间隔时间(自己规定的频率),每到达这个频率都会触发一次;假设规定频率是500MS,我们操作了1min,触发的次数就是(1 * 60 * 1000) / 500,可以理解为是减少执行频率。使用场景如:多数为监听页面元素滚动

/**
 * 节流
 * @param {fnc} function
 * @param {wait} number     触发事件的时间间隔
 * @return {function}
 */
function throttle(func, wait) {
  if (typeof func !== 'function') {
      throw new TypeError('func must be an function!')
  }
  if (typeof wait === 'undefined') wait = 500
  
  let timer = null,
    previous = 0 // 记录上一次操作的时间
    
  return function proxy(...params) {
    let self = this,
      now = new Date(), //当前这次触发操作的时间
      remaining = wait - (now - previous)
      
    if (remaining <= 0) {
      // 两次间隔时间超过wait了,直接执行即可
      clearTimeout(timer)
      timer = null
      previous = now
      func.call(self, ...params)
    } else if (!timer) {
      // 两次触发的间隔时间没有超过wait,则设置定时器
      // 让其等待remaining这么久之后执行一次「前提:没有设置过定时器」
      timer = setTimeout(function () {
        clearTimeout(timer)
        timer = null
        previous = new Date()
        func.call(self, ...params)
      }, remaining)
    }
  }
}

// 使用
function handle() {
  console.log('throttle')
}
window.onscroll = throttle(handle, 500)