Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
qinyuanf opened this issue Sep 9, 2019 · 2 comments
Open

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

qinyuanf opened this issue Sep 9, 2019 · 2 comments
Labels
小记 日常的问题总结、新技术调研等

Comments

@qinyuanf
Copy link
Owner

qinyuanf commented Sep 9, 2019

防抖和节流的理解和使用(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
Copy link
Collaborator

yuqingc commented Sep 10, 2019

👍

@yuqingc
Copy link
Collaborator

yuqingc commented Sep 10, 2019

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

@qinyuanf qinyuanf added the 小记 日常的问题总结、新技术调研等 label Sep 21, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
小记 日常的问题总结、新技术调研等
Projects
None yet
Development

No branches or pull requests

2 participants