写在最前面
在各大网络平台搜索手写防抖函数的文章或专栏,一些没有具体实现leading和trailing的功能,一些实现了但缺少详细的思维流程。
恰巧今天重温节流函数的手写,也当提醒自己写一篇文章来记录一下。
实现流程及功能
-
throttle函数的基本实现(leading=true,trailing=false)
-
leading = false, trailing = true ; leading = true, trailing = false 功能实现
-
this绑定和参数的问题
-
取消功能的实现
-
返回值的问题
补充说明
Q:为什么没有实现leading和trailing都为false的情况?
A:如果都为false可能会出现:用户输入一次之后等待结果不继续输入,但是没有输出的情况
并且引用underscore封装的throttle函数发现也没有实现这个功能
throttle函数的基本实现
function throttle(fn, interval) {
let lastTime = 0
const _throttle = function () {
const nowTime = new Date().getTime()
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
fn()
lastTime = nowTime
}
}
return _throttle
}
实现思路
-
真正执行时调用的是_throttle函数,所以throttle返回一个_throttle函数。
-
调用_throttle函数每次都要记录一下nowTime的值,将计算得到的remainTime与delay做比较,当remainTime的值小于等于delay时,意味着过了一个周期,此时执行一次fn函数。
-
将lastTime的值置为nowTime,再一次调用时会重新开始计算remainTime与delay做比较,相当于又是一次新的周期。
-
注意:当第一次点击时,由于lastTime值为0,而new Date().getTime()获取的一定是一个大数,所以会立即执行一次fn函数。
leading = false, trailing = true 和 leading = true, trailing = false 功能实现
function throttle(fn, interval, options = { leading: true, trailing: false }) {
let lastTime = 0
const { leading, trailing } = options
let timer = null
const _throttle = function () {
const nowTime = new Date().getTime()
// 处理leading
if (!leading && !lastTime) {
lastTime = nowTime
}
const remainTime = interval - (nowTime - lastTime)
// fn函数执行
if (remainTime <= 0) {
// 当在remainTime正好为0时输入
// 由于mainScript会先执行,所以可以取消计时器,回调函数不会执行
// 但是如果不是正好为0,回调函数到时执行,fn函数也执行,一共会执行两次,出现问题
if (timer) {
clearTimeout(timer)
timer = null
}
fn()
lastTime = nowTime
//如果不return会多加计时器
return
}
// 处理trailing
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
// 如果leading为false,lastTime = 0
// 如果leading为true,lastTime = nowTime
lastTime = !leading ? 0 : new Date().getTime()
fn()
}, remainTime)
}
}
return _throttle
}
实现思路
-
leading为false时:
为了在最开始就等一个interval,那么将lastTime设置为nowTime。
但如果单凭leading一个的值设置lastTime,那么每次调用_throttle都会将lastTime置为nowTime,一直等待interval。
所以加入lastTime协同判断,当lastTime的值为0时,说明是第一次调用节流函数,将lastTime置为nowTime以等待interval
-
leading=false,trailing=true
leading为false,若用户只输入一次,那么希望等到interval后也调用一次函数,所以可以考虑使用定时器:
-
当trailing为false时,开启一个定时器,当用户在此期间不输入,到时间会自动执行setTimeout中的回调,其中会执行fn函数,并将timer设置回null,
lastTime的值要根据leading来决定:
当leading为false时,将lastTime置为0,这样当用户隔了很长一段时间不输入后,再次输入会回到
if (!leading && lastTime) {lastTime = nowTime}
的判断,实现了leading为false的功能;当leading为true时,将lastTime设置为目前的时间,当用户隔了很长一段时间不输入,nowTime依旧远远大于lastTime,实现leading为true功能。
-
当开启计时器后用户依然有输入:
情况一: remainTime > 0
此时应该继续之前计时,所以要有
if (trailing && !timer)
的判断情况二:remainTime = 0
此时会执行
if (remainTime <= 0)
中的代码,即会执行fn函数,但是此时计时器也计时结束,由于setTimeout的回调函数是在宏队列中晚于mainScript中代码的执行,所以此时应该清除计时,将timer设置回null,最后return以防止再次开启定时器。
-
-
leading=true,trailing=true
其实已经实现好了!在之前
lastTime = !leading ? 0 : new Date().getTime()
里
this绑定和参数的问题
只要掌握this完全没问题
function throttle(fn, interval, options = { leading: true, trailing: false }) {
let lastTime = 0
let timer = null
const { leading, trailing } = options
const _throttle = function (...args) {
const nowTime = new Date().getTime()
if (!leading && !lastTime) {
lastTime = nowTime
}
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}
fn.apply(this, args)
lastTime = nowTime
return
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
lastTime = !leading ? 0 : new Date().getTime()
fn.apply(this, args)
}, remainTime)
}
}
return _throttle
}
取消功能的实现
function throttle(fn, interval, options = { leading: true, trailing: false }) {
let lastTime = 0
let timer = null
const { leading, trailing } = options
const _throttle = function (...args) {
const nowTime = new Date().getTime()
if (!leading && !lastTime) {
lastTime = nowTime
}
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}
fn.apply(this, args)
lastTime = nowTime
return
}
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
lastTime = !leading ? 0 : new Date().getTime()
fn.apply(this, args)
}, remainTime)
}
}
_throttle.cancel = function () {
if(timer){
clearTimeout(timer)
}
timer = null
lastTime = 0
}
return _throttle
}
返回值的问题
不能直接return result的原因:
在其中执行result = fn.apply(this,args)时,result的赋值会在几秒之后,而mainScript已经执行结束,result依然为null
function throttle(fn, interval, options = { leading: true, trailing: false }) {
let lastTime = 0
let timer = null
const { leading, trailing } = options
const _throttle = function (...args) {
return new Promise((resolve) => {
const nowTime = new Date().getTime()
// 处理leading
if (!leading && !lastTime) {
lastTime = nowTime
}
// 正常执行
const remainTime = interval - (nowTime - lastTime)
if (remainTime <= 0) {
if (timer) {
clearTimeout(timer)
timer = null
}
const result = fn.apply(this, args)
resolve(result)
lastTime = nowTime
return
}
// 处理trailing
if (trailing && !timer) {
timer = setTimeout(() => {
timer = null
const result = fn.apply(this, args)
resolve(result)
lastTime = !leading ? 0 : new Date().getTime()
}, remainTime)
}
})
}
_throttle.cancel = function () {
if (timer) {
clearTimeout(timer)
}
timer = null
lastTime = 0
}
return _throttle
}
写在最后
这是我第一次写文章,如果有不妥希望能在评论区指出~