前端防抖和节流优化

155 阅读4分钟

防抖

防抖:

页面中的按钮没有做过处理,频繁点击会触发很多次请求,造成不必要的资源浪费,所以我们需要进行防抖处理

我们假设,只要500ms (规定的高频率触发规则)内触发两次及以上,我们就识别为频繁触发,则最后把需要做的事情只做一次

防抖基础版

// 获取按钮元素
const submit = document.querySelector('#submit');

// 按钮触发后执行的方法
const func = function func() {
  console.log('按钮出发了');
}

let timer = null;
submit.onclick = function () {
  // 清除上一次设置的定时器
  clearTimeout(timer)
  // 再重新设置一个新的定时器来监听:500ms 内是否有触发第二次
  // 有: 则把之前设置的定时器清除掉,再从新设置一个即可
  // 没有: 到时间后把想要执行的方法执行即可
  timer = setTimeout(() => {
    func()
  },500)
}

项目中为了统一维护和管理,在之后类似的情形或者其他功能按钮也需要做类似处理,我们需要进行封装

防抖升级版

// 触发后需要执行的函数
const func = function func() {
  console.log('按钮出发了');
}
// 统一清除定时器
const clearTimer = function clearTimer(timer) {
  if (timer)clearTimeout(timer)
  return null;
}
/**
 * 防抖函数
 * @param {*} func 执行函数
 * @param {*} wait 等待时间
 * @returns
 */
const myDebounce = function myDebounce(func, wait) {
  if (typeof func !== 'function') throw new TypeError('func must be function')
  wait = +wait; // 将wait等待时间转换为数字
  // 如果不是数字,赋值为默认值500ms
  if(isNaN(wait)) wait = 500  
  let timer = null;
  // 如果点击按钮,传入了一些参数 params ,需要接收参数
  return function operate(...params) {
    clearTimer(timer)
    timer = setTimeout(() => {
      // 定时器中的this -> window, 所以需要通过call来修改this指向
      func.call(this,...params)
    },wait)
  }
}

// 调用
myDebounce(func,500)

防抖尊享版

const myDebounce = function myDebounce(func, wait, immediate) {
  // init params
  if (typeof func !== 'function') throw new TypeError('func must be function')
  if (typeof wait === 'boolean') {
    immediate = wait;
    wait = undefined
  }
  if (typeof immediate !== 'boolean') immediate = false
  wait = +wait;
  if (isNaN(wait)) wait = 500;

  let timer = null;
  return function operate(...params) {
    // 开始边界,第一次进来,定时器为空,immediate 为 true
    // 之后触发频率内再进来,定时器timer 就不是为空了
    let now = !timer && immediate
    // 清除定时器触发频率内之前的定时器
    clearTimer(timer)
    timer = setTimeout(() => {
      // 结束边界触发
      if (!immediate) func.call(this, ...params)
      // 最后一次触发时,定时器到时间了,函数执行,把本次定时器给清除
      clearTimer(timer)
    }, wait)
    // 开始边界触发
    if(now) func.call(this,...params)
  }
}

节流

场景

鼠标在浏览器中滚动,触发滚动事件. window.onscoll 有一个自己默认的触发频率:浏览器的最快反应时间 [ 谷歌是: 5ms -- 7ms ]

window.onscroll = function func () {
  console.log('鼠标滚动');
}

假设鼠标滚动浏览器触发频率是5ms ,当开始滚动,第一个5ms ,设置一个定时器,等待 495ms 之后,开始执行 func 假若在此期间,例如10ms ...及以后又触发了,此时如果存在一个定时器等待执行,则啥都不处理即可 到了495ms 之后,把func 执行,如果此时滚动还在继续,我们还需要在设置定时器,再间隔等待时间(wait)去执行func !

image.png 假设等待时间 wait 为 500ms 当前时间 (-) 减去 上一次触发 func 的时间 等于(=)间隔时间差

  • 时间差< 500ms
    • 有定时器不需要处理
    • 没有定时器,设置定时器去等待
  • 时间差> 500ms 立即执行 func

节流函数

注意: 当两次触发的时间间隔小于等待的时间,需要看定时器存在不存在(很重要)

  • 定时器不存在,我们才设置定时器,定时器等待的时间是 remaining (不是 wait )
  • 存在,不做处理
const clearTimer = function clearTimer(timer) {
  if (timer) clearTimeout(timer);
  return null;
}
const mythrottle = function mythrottle(func, wait) {
  if (typeof func !== 'function') throw new TypeError('func must be function')
  wait = +wait // 转成数字
  if (isNaN(wait)) wait = 500;
  let timer = null, // 定时器
    previous = 0;// 记录上一次执行
  return function operate(...params) {
    // 时间差
    let now = + new Date(); // 当前时间
    // 两次触发的时间间隔超过设定的频率,则立即执行函数
    let remaining = wait - (now - previous); // 剩余时间
    if (remaining < 0) {
      func.call(this, ...params)
      previous = + new Date();
      clearTimer(timer) // 函数返回 null,在之后还需要判断定时器存在不,不存在设置定时器重新监听
    } else if ( !timer) {
      // 时间间隔不足设定的频率间隔,而且还未设置等待的定时器,则设置定时器等待执行函数即可
      timer = setTimeout(() => {
        func.call(this, ...params)
        previous = +new Date();
        clearTimer(timer);
      }, remaining) // 一定是剩余时间
    }
    clearTimer(timer)
  }
}