面试官叫我手写一个防抖,稳啦!
前言
在JavaScript的舞台上,函数就像是那些喜欢搞怪的小丑,总是在你不经意间跳出来捣乱。你一不留神,它们就像是集体疯狂了似的,同时争先恐后地执行,把你的页面搞得乱七八糟。这时候,就需要一个“大魔术师”来驯服它们了——这就是JavaScript中的防抖!
想象一下你是一位驯兽师,而JavaScript函数就像是你的驯兽场里的各种动物,其中有些像是永远停不下来的兔子,有些则像是无法控制的疯狂狮子。而防抖就是你的秘密武器,它让你能够控制这些顽皮的“动物们”,不再让它们在你的舞台上胡闹,而是乖乖地按照你的指令表演。
或者,你可以把JavaScript函数想象成一群喜欢“开派对”的好友,他们总是在你不经意间组织聚会,搞得你焦头烂额。而防抖就像是给了你一个魔法的遥控器,只要一按下去,这些好友们就会暂时安静下来,不再频繁地来打扰你,让你有时间去做其他的事情。
又或者,你可以把JavaScript函数比喻成一群“暴躁的厨师”,他们总是在厨房里乱做一团,把你的代码搞得一团糟。而防抖就像是给了你一根魔法的鞭子,只要你挥舞一下,这些“厨师们”就会老老实实地排队等候,不再一窝蜂地冲进厨房,让你的代码变得井井有条。
无论你用怎样幽默的比喻,防抖都是JavaScript中一个非常实用的技巧,能够让你的代码更加优雅、高效。它就像是给了你一把“大魔术师”的魔杖,让你能够驯服那些调皮捣蛋的函数,让它们在你的指挥下乖乖地表演,不再像一群闹哄哄的小丑一样惹人头疼。所以,当你的JavaScript函数们再次想要在舞台上频频出现时,记得拿出你的“大魔术师”魔杖,让它们安静下来,听从你的指挥吧!
1.话题引入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>提交</button>
<script>
const btn = document.querySelector('button')
btn.addEventListener('click', () => {
console.log('提交');
})
</script>
</body>
</html>
当我们设置一个提交按钮,当我们不断点击提交的时候,也会导致多次提交,那么能不能有一种机制,让我不断提交多次,只执行最后一次呢?这便是防抖的机制。单位时间内提交多次事件,只执行最后一次。
2.防抖初级
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>提交</button>
<script>
const btn = document.querySelector('button')
function handle() {
console.log('已经完成提交');
}
btn.addEventListener('click', debounce(handle))
function debounce(fn) {
let timer = null
return function () {
clearTimeout(timer)
timer = setTimeout(fn, 1000)
}
}
</script>
</body>
</html>
我们这段代码是防抖的初级写法,能让我们意识到防抖的本质。首先我们要知道提交事件后面必须跟一个不调用的函数,但是我们的防抖函数必须传参,那么我们可以在防抖函数里面返回一个函数体,这样在执行事件提交前我们仍然没有执行防抖函数里面返回的函数。
- 这段代码和我们就是我们之前所学到的闭包,执行事件提交前,按理来说防抖函数执行完毕,里面所有的东西都应该被回收掉,但是我们返回的函数里有用到防抖函数里面的timer,因此会留下它。
- 接下来当我们触发事件时,返回函数就将执行,先清除上一个定时器,然后1秒后执行handle函数,如果在1秒内又再次触发,那么上一个将不会执行,直到在一次触发后一秒都不会触发,才会打印执行结果。这就是防抖函数的机制。单位时间内提交多次事件,只执行最后一次。
3.防抖高级
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button>提交</button>
<script>
const btn = document.querySelector('button')
function handle(e) {
console.log('已经完成提交');
console.log(this);
console.log(e);
}
btn.addEventListener('click', debounce(handle))
function debounce(fn) {
let timer = null
return function (e) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.call(this, e)
}, 1000)
}
}
</script>
</body>
</html>
初级防抖打印handle里的this和e
高级防抖打印handle里的this和e
我们需要知道两点,在我们将handle作为参数传给防抖函数时,出现了两个问题。
- handle本身this作为事件里参数要指向btn,结果指向了全局。
- handle里的e丢失了。
针对于第一种情况,我们要知道,最终的handle是被定时函数调用了,而定时函数的回调函数中this指向全局,那么我们就将handle函数放入箭头函数内,箭头函数内不含this,因此this在定时函数内做参数,而这时候的this就在返回函数内而处于定时函数外,因为是作为参数传进来的。那么这个时候this顺利指向了btn。
针对第二种情况,我们知道handle函数丧失了e是因为这个时候handle函数不再作为提交事件直接调用的函数,这个时候的e存在于返回函数中,因此我们只需要将返回函数里的e传给handle就可以了。
这样我们就完成了完整的一个防抖函数,稳啦!
4.小结
JavaScript中的防抖(Debouncing)是一种常用的优化技术,用于控制函数在连续触发时的执行频率,确保函数在指定的时间间隔内只执行一次。其应用场景和实现思路如下:
应用场景:
- 输入框搜索:当用户在输入框中输入搜索关键字时,可以利用防抖技术延迟搜索请求的发送,等待用户停止输入一段时间后再进行搜索,从而减少不必要的网络请求和服务器负载。
- 窗口调整:当窗口大小调整时,会触发resize事件,使用防抖可以延迟执行窗口调整相关的操作,确保在用户完成调整后才进行相应的处理,提高性能和流畅度。
- 按钮点击:防止用户在短时间内多次点击按钮导致重复操作,可以使用防抖确保按钮点击事件只执行一次,避免不必要的多次请求或操作。
实现思路:
- 利用定时器:在事件处理函数中设置一个定时器,在触发事件时先清除之前的定时器,然后重新设置一个新的定时器。如果在指定的时间间隔内再次触发事件,则会重新计时,直到事件停止触发,定时器超时后才执行函数。
- 函数闭包:通过闭包保存定时器的标识,确保在函数多次触发时能够正确清除和设置定时器,实现防抖效果。
- 事件监听器封装:封装一个通用的防抖函数,接收待执行的函数和延迟时间作为参数,在内部实现定时器逻辑,并返回一个新的函数作为事件监听器,用于替代原始的事件处理函数。
- 立即执行:根据需求,可以选择是否在触发事件时立即执行函数,还是在事件停止触发后执行函数。
通过以上实现思路,我们可以在JavaScript中方便地应用防抖技术,提高代码性能和用户体验,当看完这篇文章之后,如果面试官让我们手写的话,我们一定会想:稳啦!
转载自:https://juejin.cn/post/7368662916152197159