本文是我对函数节流与防抖的一点粗浅理解,如有不对,恳请指正。
在事件触发频繁如 resize、scroll、mousemove 等场景中,为了减少处理开销,采用节流或防抖限制函数的执行次数。
都是在规定时间段内频繁触发事件只执行一次函数。
功能函数 fn 在定时器内 则在固定时间段 结束 时执行,类比吟唱施法时间,打断则重来。
功能函数 fn 在定时器外 或 没有定时器 则在固定时间段 开始 时 立即 执行,类比冷却时间,释放技能后进入冷却。
节流(throttle)
定时器版
立即执行版(常用)
功能函数在定时器外:触发事件后立即执行函数再进入规定冷却时间
function throttle(fn, delay = 500) { // 指定默认值
let timer = null // 可以省略 =null
return function() {
if (!timer) {
fn.apply(this, arguments) // 注意在定时器外(冷却)
timer = setTimeout(() => {
timer = null // 不可省略
}, delay)
}
}
}
window.onscroll = throttle(() => console.log('hi'), 1000) // 业务代码
fn.apply(this, arguments) 使得 this 指向调用该功能函数 fn 的 DOM 元素。
思路:
- 每触发事件调用节流函数,如果没有计时器,就执行功能函数,并创建一个计时器,计时器计时完毕后清除自己。
- 在规定时间内不断调用节流函数,由于定时器已经存在,不触发功能函数 fn。
非立即执行版
功能函数在定时器内:触发事件后吟唱规定时间后执行函数
function throttle(fn, delay = 500) { // 指定默认值
let timer = null // 可以省略 =null
return function() {
if (!timer) {
timer = setTimeout(() => {
timer = null // 不可省略
fn.apply(this, arguments) // 注意在定时器内(吟唱)
}, delay)
}
}
}
window.onscroll = throttle(() => console.log('hi'), 1000) // 业务代码
时间戳版
触发事件后立即执行函数再进入规定冷却时间
function throttle(fn, delay = 500) {
let previous = 0
return function() {
let now = Date.now()
if (now - previous > delay) {
fn.apply(this, arguments)
previous = now
}
}
}
window.onscroll = throttle(() => console.log('hi'), 1000) // 业务代码
防抖(debounce)
非立即执行版(常用)
触发事件后吟唱规定时间后执行函数。
function debounce(fn, delay = 500) {
let timer = null // 可以省略 =null
return function() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null // 可以省略改行
}, delay)
}
}
let input = document.querySelector('input')
input.oninput = debounce(() => {
console.log(input.value) // 业务代码
}, 1000)
思路:
- 每触发事件调用防抖函数就创建一个计时器,如果新创建计时器前存在一个计时器,就清除旧的计时器,保留新创建的计时器。
- 在规定时间内不断调用防抖函数,会不断创建新的计时器替代旧的计时器。某一计时器坚持规定时间不被替代,才执行功能函数。
通俗理解:像是擂台赛一样,谁在擂台上坚持一段时间,无人挑战,就是最终的胜出者。或像是拍卖一样,谁出价坚持一段时间,无人再次出价,就是最终的竞得者。
立即执行版
触发事件后立即执行函数,进入冷却时间。
function debounce(fn, delay = 500) {
let timer = null // 可以省略 =null
return function() {
let triggerNow = !timer // 判断
if (timer) {
clearTimeout(timer)
}
if (triggerNow) {
fn.apply(this, arguments)
}
timer = setTimeout(() => {
timer = null // 不可省略
}, delay)
}
}
let input = document.querySelector('input')
input.oninput = debounce(() => {
console.log(input.value) // 业务代码
}, 1000)
合并:immediate 控制是否为立即执行版
function debounce(fn, delay = 500, immediate) {
let timer = null // 可以省略 =null
return function() {
if (timer) {
clearTimeout(timer)
}
if (immediate) {
const triggerNow = !timer // 判断
timer = setTimeout(() => {
timer = null // 不可省略
}, delay)
if (triggerNow) {
fn.apply(this, arguments)
}
} else {
timer = setTimeout(() => {
fn.apply(this, arguments)
}, delay);
}
}
}
let input = document.querySelector('input')
input.oninput = debounce3(() => {
console.log(input.value) // 业务代码
}, 1000, true)
总结
ES6 写法
节流(定时器立即执行版)
const throttle = (fn, delay = 500) => {
let timer = null
return (...args) => {
if (timer) {
return
}
fn.call(undefined, ...args)
timer = setTimeout(() => {
timer = null
}, delay)
}
}
window.onscroll = throttle(() => console.log('hi'), 1000) // 业务代码
防抖(定时器非立即执行版)
const debounce = (fn, delay = 500) => {
let timer = null
return (...args) => {
if (timer !== null) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.call(undefined, ...args)
timer = null
}, delay)
}
}
let input = document.querySelector('input')
input.oninput = debounce(() => {
console.log(input.value) // 业务代码
}, 1000)