看完本文,可以解决下面三个面试题
- 手写一个节流、防抖函数并讲解原理
- 手写一个简单搜索框,并实现搜索逻辑
- 手写一个滚动优化
本文配有完整的 demo 演示,可前往逐一测试,使用 netlify 部署,访问速度可能稍慢。
lxfriday-give-me-job-web-serve.netlify.com/
节流防抖是一种防止函数频繁无序执行的设计思想,它的核心目的是让无序变为有序,让函数执行更符合预期目的。
防抖
防抖的原理是,只要在倒计时的范围内,新触发防抖函数就会导致计时器重置,要重新等待 wait
时长之后才能执行。
简单实现
示例中绑定了 this
,在调试工具中可以看到 this
和 event 对象
function debounce(func, wait) {
let timeout
return function debounced(...args) {
const ctx = this
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
func.apply(ctx, args)
}, wait)
}
}
需要立即执行的防抖
和上面不同之处在于,在定时时间之后,只要触发就会立即执行(第一种是要等待 wait
时长),然后至少在 wait
之后才能进行下次触发。
function debounce(func, wait, immediate) {
let timeout
return function debounced(...args) {
const ctx = this
if (timeout) clearTimeout(timeout)
if (immediate) {
const callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) func.apply(ctx, args)
} else {
timeout = setTimeout(() => {
func.apply(ctx, args)
}, wait)
}
}
}
带有取消功能的防抖
取消功能实际是取消计时器。
上述两种防抖都用到了计时器,第一种如果触发防抖函数,在 wait
时间内取消(也就是 setTimeout
的匿名函数还没有执行),则和没有触发的效果一致,也就相当于什么都没做。移到灰色区域,然后马上点击取消,页面没有变化。
第二种由于只要触发防抖就会导致回调被调用,则它的取消就相当于把下一次触发的间隔给取消了,也就是没有取消的时候还需要 wait
时长才能执行回调,取消了计时器,马上就可触发防抖函数进而直接执行回调,然后再次进入倒计时。
function debounce(func, wait, immediate) {
let timeout
function debounced(...args) {
const ctx = this
if (timeout) clearTimeout(timeout)
if (immediate) {
const callNow = !timeout
timeout = setTimeout(() => {
// 这里只有在 wait 时长之后,timeout 为 null,然后触发防抖函数才能立即执行,
// 否则 callNow 为 false,不会立即执行,计时器会重新计时
timeout = null
}, wait)
if (callNow) func.apply(ctx, args)
} else {
timeout = setTimeout(() => {
func.apply(ctx, args)
}, wait)
}
}
debounced.cancel = function cancel() {
clearTimeout(timeout)
timeout = null
}
return debounced
}
我玩了会带取消的防抖,然后...
似乎在玩 debounce 中找到了乐趣。
防抖的特点是需要等待 wait
时长才能执行回调,游戏中放技能要读条比较符合防抖,释放技能需要先读条才能放出去,反复按技能键只会让技能读条一次次重来。
节流
节流分为时间戳节流和定时器节流,节流的特点是一直被触发时,每隔 wait
时长执行一次回调函数。 连续触发会按 wait 时长连续执行(这是和防抖的根本区别)。
时间戳节流
lxfriday-give-me-job-web-serve.netlify.com/throttle-ti…
时间戳节流,用最新触发的时间减去上一次回调执行的时间,如果大于等于 wait
则会执行回调,回调的执行依赖于当前节流函数被触发,鼠标不移动到灰色区域数字不可能变化。
时间戳节流在第一次触发时会立刻执行。
function throttle(func, wait) {
let previous = 0
return function throttled(...args) {
const ctx = this
const now = Date.now()
const remain = wait - (now - previous)
if (remain <= 0) {
func.apply(ctx, args)
previous = now
}
}
}
定时器节流
lxfriday-give-me-job-web-serve.netlify.com/throttle-ti…
定时器节流是在指定时间之后执行回调,触发一个定时器之后,即使再次触发节流函数,也不会导致定时器推迟执行(事件循环或者同步阻塞会导致延迟执行,在这里不用考虑)。它不会清除已经开始的定时器,而是等待定时器被执行之后才再开始下一个定时器。
function throttle(func, wait) {
let timeout = 0
return function throttled(...args) {
const ctx = this
// 如果已经是定时器定时阶段,则直接跳过,相当于忽略了触发
// 必须等到定时器到时间之后
if (!timeout) {
timeout = setTimeout(() => {
func.apply(ctx, args)
timeout = null
}, wait)
}
}
}
设计一个搜索框,并给出比较优雅的搜索反馈
以小米商城为例,搜索框下面能给出搜索建议,它具有以下几个特点:
- 输入框选中的时候会给出搜索建议
- 一直连续输入,在输入期间搜索建议不会变化,停下来马上能刷新建议
- 输入框清空的时候给出默认的搜索建议,同第一步的搜索建议
搜索结果的产生
- 点击回车来搜索
- 点击搜索建议来搜索
demo lxfriday-give-me-job-web-serve.netlify.com/debounce-se…
源码
示例的搜索框具备以下能力
- 搜索框选中时自动给出搜索提示
- 快速输入关键词时只会显示最后一次输入的关键词建议(右侧有内部的处理过程,内部时刻都在处理,没有显示是因为被防抖处理掉了)
- 点击关键词或者按回车进行搜索,对搜索进行了防抖处理,频繁按回车会一直推迟到最后一次按回车后的 0.5 秒显示搜索结果
- 回车搜索时,前后搜索的词相同则会自动阻止搜索
连续滚动优化
demo lxfriday-give-me-job-web-serve.netlify.com/debounce-sc…
源码
防抖处理过后效果非常明显,连续滚动时,左边弹窗会一直反馈,右边防抖的弹窗只在停下来了才弹出。
参考
欢迎大家关注我的掘金和公众号,算法、TypeScript、React 及其生态源码定期讲解。