一、你是如何实现防抖的?讲一下思路。
拿坐电梯来说,我们知道电梯关门的机制是持续的一段时间内都没有物体经过门口,假设是5秒,5秒之内都没有物体在门口才会关门,如果从最后一次有物体经过门口的5秒之内又有物体出现,那就重新计时5秒,再重新判断能否关门。这就是防抖。放到js里说就是,你尽管触发事件,但是我一定只在你最后一次事件触发后的 n 秒才执行。
普通版
首先考虑接收的参数,一个是目标函数fn,一个是延迟时间delay;第二个考虑返回值,最终我们返回结果是要绑定对应的事件的, 所以返回值一定是一个新的函数。内部实现的思路是:
我们可以在debounce函数中开启个定时器, 定时器的延迟时间就是delay
并且每开启一个定时器, 我们都需要先将上一次的定时器取消掉,也就是事先要执行一个判断,如果timer有值的话就置为null;在定时器执行完之后,也要将timer置空
function mydebounce(fn, delay) {
// 1.创建一个变量, 用于记录上一次定时器
let timer = null
// 2.触发事件时执行的函数
const _debounce = function() {
// 2.1第一次timer为null, 所有我们需要判断timer有值时清除定时器
if (timer) clearTimeout(timer)
// 2.2延迟执行传入的fn回调
timer = setTimeout(() => {
fn.apply(this)
// 2.3函数执行完成后, 我们需要将timer重置
timer = null
}, delay)
}
// 返回一个新的函数
return _debounce
}
进阶版-增加取消操作:例如用户在表单输入的过程中,返回了上一层页面或者关闭了页面, 就意味着我们这次延迟的网络请求没有必要继续发生了
// 用函数表达式声明函数,然后给_debounce添加一个取消防抖的方法:直接将存在的timer清空就行
_debounce.cancel = function() {
if (timer) clearTimeout(timer)
}
进阶版-增加立即执行功能:有些场景需要第一次输入时, 立即执行, 后面的输入再使用防抖延迟执行--如果需要此功能, 那么传入第三个参数 immediate 参数来设定我们防抖函数是否需要立刻执行;还需要在 函数中定一个标识flag ,这个标识记录函数是否被立即执行过,然后根据这个变量去完成我们的需求
定义一个 flag 去做是否立即执行的开关逻辑,这里直接用传入进来的 immediate 不就好了吗,同样可以达到效果,为何要多此一举?
尽量不要修改传给函数的形参值,因为这写值是调用函数传进来的,如果直接修改就违反了单向传递数据流的设计了;如果因为内部的修改导致外部变量变化,说不定还会出现BUG,所以最好函数内定义自己的变量去维护里面的逻辑
// 1.设置第三个传入的参数, 并且默认值为false
function mydebounce(fn, delay, immediate = false) {
let timer = null
// 2.定义变量, 用于记录状态
let isInvoke = false
const _debounce = function(...args) {
if (timer) clearTimeout(timer)
// 3.第一次执行不需要延迟
if (!isInvoke && immediate) {
fn.apply(this, args)
isInvoke = true //表示立即执行过了
return
}
timer = setTimeout(() => {
fn.apply(this, args)
timer = null
// 4.重置isInvoke
isInvoke = false //表示接下来的第一次还是要立即执行
}, delay)
}
_debounce.cancel = function() {
if (timer) clearTimeout(timer)
// 取消也需要重置
timer = null
isInvoke = false
}
// 返回一个新的函数
return _debounce
}
二、节流
还是拿坐电梯来说,从第一个人上电梯开始,经过5秒,电梯门都会关闭,不管上去多少人。这就是节流。
实现节流函数, 我们使用定时器是不方便管理的, 实现节流函数我们采用时间戳
我们获取一个当前时间nowTime, 我们使用new Date().gettime()方法获取, 在设定一个开始时间startTime, 等待时间waitTime
waitTime = interval - (nowTime - startTime), 当前的时间减去开始的时间得到结果, 再使用间隔时间减去这个结果, 就可以得到等待时间
得到等待时间我们在进行判断, 如果等待时间小于等于0, 那么就可以执行回调函数
开始时间startTime我们初始值为0就好, 当第一次执行时, nowTime获取的时间戳是一个非常大的值, 得到的结果waitTime是负值, 所以第一次执行节流函数, 一定会立即执行, 这也符合我们要封装的效果
function mythrottle(fn, interval) {
// 1.定义变量保记录开始时间
let startTime = 0
const _throttle = function() {
// 2. 获取当前时间
const nowTime = new Date().getTime()
// 3.计算需要等待的时间
const waitTime = interval - (nowTime - startTime)
// 4.当等待的时间小于等于0时, 执行回调函数
if (waitTime <= 0) {
fn()
// 并让开始时间等于现在时间
startTime = nowTime
}
}
return _throttle
}