简介
在进行窗口的resize
、scroll
,输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。
此时我们可以采用debounce
(防抖)和throttle
(节流)的方式来减少调用频率,同时又不影响实际效果。
- 函数防抖:将几次操作合并为一次操作进行。原理是维护一个计时器,规定在
delay
时间后触发函数,但是在delay
时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。 - 函数节流:使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
区别:
- 函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数
- 函数防抖只是在最后一次事件后才触发一次函数
- 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次
Ajax
请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
防抖debounce
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时
对于短时间内连续触发的事件,防抖的含义就是让某个时间期限内,事件处理函数只执行一次。
/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn, delay) {
let timer = null // 借助闭包
return function() {
if (timer) {
// 进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
clearTimeout(timer)
}
timer = setTimeout(fn, delay)
}
}
// 处理函数
function handle() {
console.log(Math.random())
}
// 滚动事件
window.addEventListener('scroll', debounce(handle, 1000))
如果处理函数带参数,需要修改下防抖函数
/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn, delay) {
let timer = null // 借助闭包
return function() {
if (timer) {
// 进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(self, arguments)
}, delay)
}
}
节流throttle
当持续触发事件时,保证一定时间段内只调用一次事件处理函数
时间戳实现
- 再怎么频繁地触发事件,也都是每
delay
时间才执行一次- 而当最后一次事件触发完毕后,事件也不会再被执行了
function throttle(fn, delay) {
let prev = Date.now()
return function() {
const args = arguments
const now = Date.now()
if (now - prev >= delay) {
fn.apply(this, args)
prev = Date.now()
}
}
}
// 处理函数
function handle() {
console.log(Math.random())
}
// 滚动事件
window.addEventListener('scroll', throttle(handle, 1000))
定时器实现
- 当触发事件的时候,我们设置一个定时器
- 再次触发事件的时候,如果定时器存在,就不执行
- 直到
delay
时间后,定时器执行执行函数,并且清空定时器,这样就可以设置下个定时器- 当第一次触发事件时,不会立即执行函数,而是在
delay
秒后才执行。- 而后再怎么频繁触发事件,也都是每
delay
时间才执行一次。- 当最后一次停止触发后,由于定时器的
delay
延迟,可能还会执行一次函数
function throttle(fn, delay) {
let timer = null
return function() {
const args = arguments
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
}, delay)
}
}
}
// 处理函数
function handle() {
console.log(Math.random())
}
// 滚动事件
window.addEventListener('scroll', throttle(handle, 1000))
时间戳+定时器
- 在节流函数内部使用开始时间
startTime
、当前时间curTime
与delay
来计算剩余时间remaining
- 当
remaining<=0
时表示该执行事件处理函数了(保证了第一次触发事件就能立即执行事件处理函数和每隔delay
时间执行一次事件处理函数)- 如果还没到时间的话就设定在
remaining
时间后再触发 (保证了最后一次触发事件后还能再执行一次事件处理函数)- 当然在
remaining
这段时间中如果又一次触发事件,那么会取消当前的计时器,并重新计算一个remaining
来判断当前状态。
function throttle(fn, delay) {
let timer = null
let startTime = Date.now()
return function() {
const curTime = Date.now()
const remaining = delay - (curTime - startTime)
const args = arguments
clearTimeout(timer)
if (remaining <= 0) {
fn.apply(this, args)
startTime = Date.now()
} else {
timer = setTimeout(fn, remaining)
}
}
}
// 处理函数
function handle() {
console.log(Math.random())
}
// 滚动事件
window.addEventListener('scroll', throttle(handle, 1000))
发表评论