前言:
这两天看面试题,又遇到了防抖和节流,之前也是似懂非懂,于是到处看如何实现防抖和节流,可是很可惜,大部分只有代码没有实际场景和注释,于是自己弄懂后记录下来。
防抖函数
- 定义:顾名思义,在频繁触发的前提下,让函数只在特定的时间内没有触发执行条件才执行一次代码。
- 使用场景:频繁操作点赞和取消点赞,因此需要获取最后一次操作结果并发送给服务器。
自己模拟了一个使用场景,即点击提交按钮会触发ajax请求,但是如果用户频繁点击提交按钮,难道就要触发n次吗,显然不可取。
- 那么如何做呢?先写下初始代码。通过 console.log(1) 去模拟ajax 请求的场景。现在只要点击按钮就会打印 1 ,n 次点击就打印 n 次 1 。
<input type="text"> <button id="submit">提交</button>
let btn=document.getElementById('submit') btn.addEventListener('click',submit) function submit(){ console.log(1) }
- 有了场景,就要开始思考了,如何让 submit 函数防抖呢?首先给它包装一个 debounce 函数。但是这里有一个问题,在页面刷新时,会发现浏览器会自动打印 1 和 debounce。这是为什么呢?
btn.addEventListener('click',debounce(submit,1000)) function debounce(fn,delay){ fn() console.log("debounce") }
-
这是因为 addEventListener 中接收的第二个参数是立即执行函数,所以会导致 debounce 函数被执行。
-
那么就得想办法如何让 debounce 不执行。其实改写为回调函数即可。
function debounce(fn,delay){ return function(){ fn() } }
- 接着开始思考,如何在点击按钮时,让函数延迟执行呢?
function debounce(fn,delay){ return function(){ setTimeout(() => { fn() }, delay); } }
- 可是光延时哪儿行,该触发 n 次还是触发 n 次。就得想办法在第二次点击时让计时器重新计时。首先在回调函数外面定义一个 timer 用来接收计时器,由于 debounce 在页面加载时就会自动执行,所以 timer 初始值就是 null , 并且在后续点击按钮时,是直接触发的回调函数,不会去重新定义 timer 。
function debounce(fn,delay){ let timer=null return function(){ if(timer){ clearTimeout(timer) } timer=setTimeout(() => { fn() }, delay); } }
-
大致上,已经完成了防抖的基本操作,也能理解了,但是会有几个小问题。
-
关于 submit 函数的 this 指向问题。可以发现这里的 this 实际是 window 对象,为什么呢?这是因为 submit 是在计时器中被调用的。可是计时器中的 this 为什么指向按钮呢,这是因为计时器是箭头函数,箭头函数是不具备 this 的,this 取决于外层的函数,这里外层函数为回调函数,而这个回调函数指向 btn 。那么 fn() 是直接调用,所以它的 this 指向 window 。
function submit(){ console.log(this); } function debounce(fn,delay){ let timer=null return function(){ if(timer){ clearTimeout(timer) } timer=setTimeout(()=>{ console.log(this); fn() }, delay); } }
- 解决 submit 的 this 指向问题。这里只需要使用 apply 函数去改变 this 指向即可。
timer=setTimeout(()=>{ console.log(this); fn.apply(this) }, delay);
- 关于事件对象的获取。如果改写为如下就可以获取到 e 。
function submit(){ console.log(e); } function debounce(fn,delay){ return function(e){ console.log(e); } }
- 按照防抖函数的写法。debounce 可以获取到 e ,而 submit 是获取不到的。所以要思考如何传递参数。
function submit(){ console.log(e); } function debounce(fn,delay){ let timer=null return function(e){ if(timer){ clearTimeout(timer) } timer=setTimeout(()=>{ console.log(e); fn.apply(this) }, delay); } }
- arguments 可以用来传递参数,在回调函数中,arguments 就是传递给函数的所有参数集合。
function submit(e){ console.log(e); } function debounce(fn,delay){ let timer=null return function(){ if(timer){ clearTimeout(timer) } timer=setTimeout(()=>{ fn.apply(this,arguments) }, delay); } }
具体 html 代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>防抖</title> </head> <body> <input type="text"> <button id="submit">提交</button> <script> let btn=document.getElementById('submit') btn.addEventListener('click',debounce(submit,1000)) function submit(e){ console.log(11111); } function debounce(fn,delay){ let timer=null return function(){ if(timer){ clearTimeout(timer) } timer=setTimeout(()=>{ fn.apply(this,arguments) }, delay); } } </script> </body> </html>