函数节流与函数防抖
最近由于处于互联网大厂的秋招季节,因此这些天都在看前端性能优化和算法方面的知识。在性能优化方面,看了网上的一些文章,同时看完了《高性能网站建设指南》和《高性能JavaScript》两本书,颇有收获,可以参看这篇文章,主要是一些前端性能优化方面的总结。传送门:前端性能优化最佳实践
这篇文章主要是讲函数节流与函数防抖相关知识的。虽然在上面两本书里面没有谈及这两方面的内容,但是我觉得,对JS常用事件进行节流或者防抖的处理是属于性能优化方面的。
目的
实现了这两个功能函数之后发现,节流同防抖在实现过程可能不太一样,但是目的和本质都是一样的:
需要节流或者防抖的函数通过定时器进行间隔调用,目的是只有在执行函数的请求停止了一段时间之后才执行。
防抖和节流主要运用了时间差、闭包、定时器来处理。
相同点
节流和防抖都可以采用闭包的形式和定时器间隔调用函数的方式来实现。这主要运用了闭包的一个特性:能够记住并访问所在的此法作用的标识符。如果对闭包不了解的可以看看这个回答:什么是闭包
函数节流
function throttle () {
var time = null
...
// 闭包
return function () {
window.setTimeout(time)
time = window.setTimeout(() => {...})
}
}
函数防抖
function debounce () {
var time = null
// 闭包
return function () {
if (!time) {
time = window.setTimeout(later, delay)
...
}
}
}
用途
个人认为,能够使用函数节流的场景,也可以用于函数防抖。能够使用函数防抖的场景,也适用于函数节流。
在为某个DOM节点绑定事件时,有些事件会不断的触发浏览器进行计算。如resize, mousemove, keydown, keypress, keyup, touchmove等。对于这些时间都可以使用节流或者防抖进行处理。
函数防抖
最基本的防抖实现,是没有加入各种条件判断的时候。当然,阅读源码的一个方法也是跳过各种if判断。
function debounce (fn, option) {
var [time, context, args, result] = []
let setting = {
delay: 300 // 延迟delay秒之后间隔执行函数
}
option = Object.assign({}, setting, option)
let later = () => {
result = fn.apply(context, args)
return result
}
return function () {
args = arguments
context = this
if (!time) {
time = window.setTimeout(later, option.delay)
}
}
}
执行以上的函数会发现,只能执行一次。因此,我们需要给一个时间差,根据时间差与delay的比较,来判断是否需要重启定时器。
function debounce (fn, option) {
var [time, start, currStart, context, args, result] = []
let setting = {
delay: 300
}
option = Object.assign({}, setting, option)
let later = () => {
let currStart = +new Date() - start
if (option.delay > currStart) {
time = window.setTimeout(later, option.delay)
} else {
time = null
result = fn.apply(context, args)
}
}
return function () {
args = arguments
context = this
start = +new Date() // 反复调用事件的时间
if (!time) {
time = window.setTimeout(later, option.delay)
}
}
}
如果希望第一次调用事件时就执行函数,而不是等待delay的时间。可以传入immediate参数。
// fn需要防抖的函数, option可选
function debounce (fn, option) {
let [time, result, start, context, args] = []
let setting = {
delay: 300,
immediate: false // 第一次调用事件时是否要立即执行,默认为不立即执行
}
// setting为默认参数, 如果传入option, 则覆盖setting参数。
option = Object.assign({}, setting, option)
let later = () => {
let currStart = +new Date() - start
if (option.delay > currStart && currStart >= 0) {
time = window.setTimeout(later, option.delay)
} else {
time = null
result = fn.apply(context, args)
}
}
return function () {
args = arguments
context = this
start = +new Date()
if (!time) {
time = window.setTimeout(later, option.delay)
}
// 调用事件时立即执行
if (option.immediate) {
result = fn.apply(context, args)
}
return result
}
}
在用在resize事件时
function event (e) {
console.log(e)
}
let a = debounce(event, {
delay: 500,
immediate: fasle
})
window.addEventListener('resize', e => {
a(e)
})
函数节流
函数节流的实现原理与防抖大同小异。也是通过闭包和定时器来实现。但节流会在一定时间内重复调用时,将原来的定时器清除掉。假如在500秒内重复调用了resize时间,那么只有在最后一次等待delay时间才会执行。
function throttle (fn, option) {
let time = null
let start = null
let setting = {
delay: 300,
mustRunTime: 500, // 在500内必须执行。如在resize事件时,按住不放超过500ms之后就必须执行函数。
immediate: false
}
option = Object.assign({}, setting, option)
return function () {
let args = arguments
let context = this
let currStart = +new Date()
if (!start) {
start = currStart
}
// 初始调用resise函数时立即执行函数,而不用等待delay的时间
if (option.immediate || currStart - start > option.mustRunTime) {
fn.apply(context, args)
option.immediate = false
start = currStart
} else {
window.clearTimeout(time)
time = window.setTimeout(() => {
fn.apply(context, args)
}, option.delay)
}
}
}
节流的调用方式与防抖相同
function event (e) {
console.log(e)
}
let a = throttle(event, {
delay: 500,
mustRunTime: false,
immediate: fasle
})
window.addEventListener('resize', e => {
a(e)
})
最后
最后想说的是,函数节流与防抖的目的都是相同的,在执行函数的请求停止了一段时间之后才执行。
但是我在节流上做了处理,如果超过一定时间就会立即执行函数。
如果需要避免某些事件的调用引出浏览器的多次计算,我更加倾向于使用函数节流的方式来实现。本质上,两种方式都可以哒。