- 作者:陈大鱼头
- github: KRISACHAN
- 链接:github.com/YvetteLau/S…
- 背景:最近高级前端工程师 刘小夕 在 github 上开了个每个工作日布一个前端相关题的 repo,怀着学习的心态我也参与其中,以下为我的回答,如果有不对的地方,非常欢迎各位指出。
防抖(debounce)
防抖是什么?
防抖是什么?防抖就是防止抖动,例如小朋友喜欢多动,停不下来,然后做家长的打一顿,让小朋友安静下来,这种行为就叫做防抖。
(~o ̄3 ̄)~
抖个机灵,哈哈哈~
抖动是一个很常见的控制函数在一定时间内执行多少次的技巧,其实就是确定了函数执行的最小间隔,如果还在这个间隔内触发函数,则重新计算。
举个栗子~
你家有七个儿子,大儿子叫大娃,二儿子叫二娃,三儿子叫三娃,四儿子叫四娃,五儿子叫五娃,六儿子叫六娃,七儿子叫七娃。由于七个孩子还小,他们的衣服你只能用手洗,一洗洗七套,一次要一个小时。如果洗到一半,你的七个孩子给你过来捣乱把衣服弄脏,那么你又得花一个小时去洗,不捣乱,那么你一个小时之后你的孩子衣服就又脏了,又可以再花一个小时来洗衣服了。
具体实现
鱼头注:以下代码来自优秀的JS库 lodash
// 判断是不是个对象
function isObject(value) {
const type = typeof value
return value != null && (type == 'object' || type == 'function')
}
/*
* 创建一个 debounced 函数,延迟调用 func 直到 wait 之后
* cancel 方法用于取消 debounced
* flush 方法用于立即调用
*/
function debounce(func, wait, options) {
let lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime
let lastInvokeTime = 0
let leading = false
let maxing = false
let trailing = true
// 绕过 requestAnimationFrame ,显式地设置 wait = 0
const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function')
if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
wait = +wait || 0
if (isObject(options)) {
leading = !!options.leading
maxing = 'maxWait' in options
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
trailing = 'trailing' in options ? !!options.trailing : trailing
}
function invokeFunc(time) {
const args = lastArgs
const thisArg = lastThis
lastArgs = lastThis = undefined
lastInvokeTime = time
result = func.apply(thisArg, args)
return result
}
function startTimer(pendingFunc, wait) {
if (useRAF) {
root.cancelAnimationFrame(timerId);
return root.requestAnimationFrame(pendingFunc)
}
return setTimeout(pendingFunc, wait)
}
function cancelTimer(id) {
if (useRAF) {
return root.cancelAnimationFrame(id)
}
clearTimeout(id)
}
function leadingEdge(time) {
// 重置任何 最大等待时间的计时器
lastInvokeTime = time
// 启动计时器
timerId = startTimer(timerExpired, wait)
// 返回调用结果
return leading ? invokeFunc(time) : result
}
function remainingWait(time) {
const timeSinceLastCall = time - lastCallTime
const timeSinceLastInvoke = time - lastInvokeTime
const timeWaiting = wait - timeSinceLastCall
return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting
}
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime
const timeSinceLastInvoke = time - lastInvokeTime
// 判断是否应该调用函数
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
}
function timerExpired() {
const time = Date.now()
if (shouldInvoke(time)) {
return trailingEdge(time)
}
// 重置定时器
timerId = startTimer(timerExpired, remainingWait(time))
}
function trailingEdge(time) {
timerId = undefined
// 如果我有最后一个参数,就意味着函数需要调用
// 至少执行一次防抖
if (trailing && lastArgs) {
return invokeFunc(time)
}
lastArgs = lastThis = undefined
return result
}
function cancel() {
if (timerId !== undefined) {
cancelTimer(timerId)
}
lastInvokeTime = 0
lastArgs = lastCallTime = lastThis = timerId = undefined
}
function flush() {
return timerId === undefined ? result : trailingEdge(Date.now())
}
function pending() {
return timerId !== undefined
}
function debounced(...args) {
const time = Date.now()
const isInvoking = shouldInvoke(time)
lastArgs = args
lastThis = this
lastCallTime = time
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime)
}
if (maxing) {
// 处理循环调用
timerId = startTimer(timerExpired, wait)
return invokeFunc(lastCallTime)
}
}
if (timerId === undefined) {
timerId = startTimer(timerExpired, wait)
}
return result
}
debounced.cancel = cancel
debounced.flush = flush
debounced.pending = pending
return debounced
}
复制代码
使用方法如下:
var 打印一些东西啦 = function (event) {
console.log(event);
};
document.querySelector('html').addEventListener('click', debounce(打印一些东西啦, 1000));
复制代码
适用场景
以下就举几个我们日常开发中可能会用上 debounce 的栗子:
- 输入框搜索,当用户停止搜索一段时间之后再执行请求操作,防止多次触发而给服务端带来压力
- 防止重复触发 resize 或者 scroll 事件
- 还有吧,但是懒得动脑了~
节流(throttle)
节流是什么?
节流: 节流就是流体在管道内流动,突然遇到截面变窄,而使压力下降的现象。节制流入或流出,尤指用节流阀调节。
出处:语出《荀子·富国》:“百姓时和、事业得叙者,货之源也; 等赋府库者,货之流也。故明主必谨养其和,节其流,开其源,而时斟酌焉,潢然使天下必有馀而上不忧不足。”后以“开源节流”指开辟财源,节约开支。
其实作用跟定义就是上面的意思,在日常开发中,我们经常会有频繁调用或执行某一函数的需求,其实按实际用户情况,可能 100ms 甚至 1s 调用一次就OK,所以需要用到函数节流,在一定的时间间隔之后再执行函数。
使用场景
场景很多,常见的有:
-
屏幕尺寸变化时页面内容的变动,执行相应逻辑;
-
监听鼠标滚动时间,执行相应逻辑;
-
监听重复点击时的时间,执行相应逻辑
具体实现
下面依然是 lodash
的代码实现:
function isObject(value) {
const type = typeof value
return value != null && (type == 'object' || type == 'function')
}
function throttle(func, wait, options) {
let leading = true
let trailing = true
if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading
trailing = 'trailing' in options ? !!options.trailing : trailing
}
return debounce(func, wait, {
leading,
trailing,
'maxWait': wait,
})
}
复制代码
使用方法如下:
var 打印一些东西啦 = function (event) {
console.log(event);
};
document.querySelector('html').addEventListener('click', throttle(打印一些东西啦, 1000));
复制代码
如果你、喜欢探讨技术,或者对本文有任何的意见或建议,你可以扫描下方二维码,关注微信公众号“ 鱼头的Web海洋”,随时与鱼头互动。欢迎!衷心希望可以遇见你。