qinyuanf / front-end-Weekly

坚持,一种可以养成的习惯
MIT License
12 stars 5 forks source link

防抖和节流的理解和使用 #5

Open qinyuanf opened 5 years ago

qinyuanf commented 5 years ago

防抖和节流的理解和使用(by 元丰)

防抖

理解

当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时。也就是说当一个用户一直触发这个函数,且每次触发函数的间隔小于既定时间,那么防抖的情况下只会执行一次。

案例

案例一: 搜索框的搜索联想,如果每次键盘输入都不停地向后端发送请求,用户量大且操作频繁,一般情况下后端肯定扛不住(除非公司真有钱),理想状态应该是等待用户输入操作结束或设置一个时间间隔,在该时间间隔后不再输入则发送请求。

案例二: 不断地调整浏览器的窗口大小,通过监听页面的 resize 事件进行页面适配。根据最终呈现页面的宽高进行 dom 渲染(这种情形一般是使用防抖,只需要判断最后一次的变化情况)

原理解析

  1. debounce 函数封装后,返回内部函数;
  2. 每次事件触发后都会清除当前的 timer 然后重新设置超时并调用。这会导致每一次高频事件都会取消前一次的超时调用,导致事件处理程序不能被触发;
  3. 只有在高频事件停止触发后,最后一次的事件触发的超时调用才能在 delay 事件后调用

源码解析

// 防抖
const debounce = (fn, delay) => {
  // 设置一个 timer
  let timer = null

  return function () {
    // 获取当前 return 函数作用域和参数
    const args = arguments
    const vm = this
    // 清除正在执行的函数
    clearTimeout(timer)
    timer = setTimeout(function () {
      fn.apply(vm, args)
    }, delay)
  }
}

// 如何调用
const input = document.getElementById('debounce')
input.addEventListener('input', debounce((e) =>{
  console.log(`向后端发送请求,参数为 ${e.target.value}`)
}, 1000))

节流

理解

当持续触发事件时,保证在一定时间内只调用一次事件处理函数,意思是一个用户一直触发这个函数,且每次触发小于既定值,函数节流会每隔这个时间调用一次

案例

案例一: 抢红包的按钮,其实无论点击频率有多快,发送到后端的请求数量都是有一定限制,并不是每一次点击都会到后端

案例二: 射击游戏(CF)的开枪键点击频率,并不是点击越快,打出的子弹越多,枪的射速是固定的(原谅我小时候的天真)

原理解析

  1. 利用 时间戳 实现,让第一次触发事件时执行一次回调函数,此后每隔一段固定时间执行一次,缺点是小于该固定时间内事件是不执行的(如最后一次触发可能小于该固定时间)
  2. 利用 定时器 实现,第一次触发事件是不执行回调函数的,延迟一段时间后再执行回调函数并立即清除定时器
  3. 期待的效果是 第一次触发事件执行回调函数,最后一次触发也可以执行一次回调函数

源码解析

// 节流,时间戳 + 定时器
const throttle = (fn, delay) => {
  let timer = null
  // 开始时间
  let startTime = Date.now()

  return function () {
    const endTime = Date.now()
    // 剩余时间
    const remainTime = delay - (endTime - startTime)
    const vm = this
    const args = arguments
    clearTimeout(timer)
    if (remainTime <= 0) {
      fn.apply(vm, args)
      startTime = Date.now()
    } else {
      timer = setTimeout(fn, remainTime)
    }
  }
}

应用

// 在 vue 中使用 lodash 中的 debounce 与 throttle
<template>
  <div @click="handleExchange">兑换</div>
</template>

<script>
import debounce from 'lodash/debounce' 
export default {
  methods: {
    handleExchange: debounce(function () {
      // do something
    }, 300)
  }
}
</script>
yuqingc commented 5 years ago

👍

yuqingc commented 5 years ago

补充一个节流的小案例:滚动屏幕到底部加载更多时,不用每次 onScroll 的时候都检查到底部的距离,可以用 throttle 来周期性的检查当前位置到底部的距离。(参考 Demo