前言
在实际开发过程中, 我们在进行窗口的resize、scroll, input的内容校验、表单提交按钮的触发等操作时,如果事件处理函数的调用频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。
此时我们可以采用防抖(debouce)和节流(throttle)的方式来减少调用频率,同时又不影响实际效果。
函数防抖
定义
当持续触发事件时,在一定时间内没有再触发事件, 则执行一次处理函数,如果设定的时间未到来前又触发了事件,则重新计时。
实例解析
如下图, 当我们持续触发scroll事件,并不执行handle函数,当1000毫秒内没有再触发scroll事件时, 才开始执行一次handle函数(即延时触发scroll事件)。
应用场景
- 在点赞、输入框校验、取消点赞、创建订单等发送网络请求的时候,如果我们连续点击按钮,可能会发送多次请求。这个对于后台来说是不允许的。(涉及到会频繁调用接口的情景下, 延迟时间一般设置300ms)
- 在鼠标每次 resize/scroll 触发统计事件
函数封装
es5写法
/**
* 防抖函数
* @param {function} func 需要执行的函数
* @param {Number} wait 延迟时间
* @param {Boolean} immediate 是否立即执行
*/
function debounce (func, wait = 0, immediate) {
var timer
if (typeof func !== 'function') {
throw new TypeError('debounce的第一个参数类型为funtion')
}
return function () {
var context = this
var args = arguments
if (timer) clearTimeout(timer) // 清除定时器
// 立即执行
if (immediate) {
var callNow = !timer
timer = setTimeout(function () {
func.apply(context, args)
}, wait)
callNow && func.apply(context, args)
} else {
// 非立即执行
timer = setTimeout(function () {
func.apply(context, args)
}, wait)
}
}
}
es6(剪头函数写法)
/**
* 防抖函数
* @param {function} func 需要执行的函数
* @param {Number} wait 延迟时间
* @param {Boolean} immediate 是否立即执行
*/
function debouce(func, wait = 0, immediate) {
// 判断传递的参数类型
if(typeof func !== 'function') {
throw new TypeError('debounce的第一个参数类型为funtion')
}
let timer;
return (...arg) => {
// 清除定时器
timer && clearTimeout(timer)
// 立即执行
if(immediate) {
const callNow = !timer
timer = setTimeout(() => {
func(...arg)
}, wait)
callNow && func(...arg)
} else {
// 非立即执行
timer = setTimeout(() => {
func(...arg)
}, wait)
}
}
}
使用
// 处理函数
function handle() {}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000));
函数节流
定义
当持续触发事件时, 在一定时间内只调用一次事件处理函数。
实例解析
如下图,持续触发scroll事件时,并不立即执行handle函数,每隔1000毫秒才会执行一次handle函数。
简单粗暴的解释就是在一段时间内不管你触发多少次事件, 我就只执行一次事件处理函数。
应用场景
- DOM 元素的拖拽功能实现(mousemove)
- 搜索联想(keyup)
- 计算鼠标移动的距离(mousemove)
- Canvas 模拟画板功能(mousemove)
- 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
- 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次.
函数封装
es5-时间戳-非立即执行版本
/**
* 节流函数
* @param {*} func 事件处理函数
* @param {*} wait 延迟事件
* @returns
*/
function throttle (func, wait = 0) {
var prev = Date.now()
return function () {
var context = this
var args = arguments
var now = Date.now()
if (now - prev >= wait) {
func.apply(context, args)
prev = Date.now()
}
}
}
es5-定时器版本
/**
* 节流函数
* @param {*} func 事件处理函数
* @param {*} wait 延迟事件
* @returns
*/
function throttle (func, wait = 0) {
var timer = null
return function () {
var context = this
var args = arguments
if (!timer) {
timer = setTimeout(function () {
func.apply(context, args)
timer = null
}, wait)
}
}
}
总结
函数防抖: 将几次操作合并为一次操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
函数节流: 使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
区别: 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。