节流的原理很简单:如果你持续触发事件,每隔一段时间,只执行一次事件
关于节流的实现,有两种主流的实现方式,一种是使用时间戳,另一种是设置定时器。
使用时间戳
当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设置为 0),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
/* 第一版 */
function throttle(fn, delay) {
// 初始时间戳(默认为 0)
let previous = 0
return function(...args) {
const _self = this
// 获取当前时间戳
const now = +new Date()
if (now - previous > delay) {
fn.apply(_self, args)
previous = now
}
}
}
使用定时器
当触发事件的时候,我们设置一个定时器,在触发事件的时候,如果定时器存在,就不执行,直到定时器执行,清空定时器,这样才可以设置下一个定时器。
/* 第二版 */
function throttle(fn, delay) {
return function(...args) {
const _self = this
if (fn.timeId) {
fn.timeId = setTimeout(() => {
fn.apply(_self, args)
fn.timeId = null
}, delay)
}
}
}
比较两个方法:
- 第一种事件会立刻执行,第二种事件会在执行时间后第一次执行。
- 第一种事件停止后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件。
组合版(立即执行,停止触发后再执行一次)
/* 第三版 */
function throttle(fn, delay) {
let previous = 0
return function(...args) {
const _self = this
const now = +new Date()
// 下次触发 fn 剩余的时间
const remaining = delay - (now - previous)
if (now -previous > delay) {
if (fn.timeId) {
clearTimeout(fn.timeId)
fn.timeId = null
}
fn.apply(_self, args)
previous = now
} else if (!fn.timeId) {
fn.timeId = setTimeout(() => {
fn.apply(_self, args)
previous = +new Date()
fn.timeId = null
}, remaining)
}
}
}