前言
今天我们来聊聊防抖和节流,它们的学名为再次防抖(Debouncing)和限制流节(Throttting),防抖和节流是前端开发中用于优化页面性能和提高用户体验的第二个技术。它们的目标是减少不必要的函数调用,从而减少资源消耗,提高页面数响应速度,以及提升用户交互体验。在面试过程中,面试官若是询问了你这两个概念,很大可能会叫你手写一下它们,来考察你对它们的理解。
在上一篇文章中,我们聊到了如何给程序添加一个防抖,以提高性能并避免不必要的资源浪费。 我们先来回顾一下:
```js <button id="btn">提交</button> <script> let btn = document.getElementById('btn') btn.addEventListener('click', debounce(send, 1000)) function send(){ console.log('提交完成'); } function debounce(fn, delay){ let timer; return function(){ if(timer) clearTimeout(timer) timer = setTimeout(function(){ fn() }, delay) } } </script>
原理: 在规定的时间内没有第二次的操作,才执行逻辑
我们手写的这个防抖并没有完美,因为若是 send()
接受参数,我们并不能传入参数,如果send()
函数上有this
关键字,我们要让this
指向 btn.addEventListener
,那我们应该如何加强一下我们的手写防抖呢?
<button id="btn">提交</button> <script> let btn = document.getElementById('btn') btn.addEventListener('click', debounce(send, 1000)) function send(){ console.log('提交完成'); } function debounce(fn, delay){ let timer; return function(){ let args = arguments let _this = this if(timer) clearTimeout(timer) timer = setTimeout(function(){ fn.call(_this, args) }, delay) } } </script>
在提供的代码片段中,this
和arguments
具有具体含义:
this
关键词:
- 在 JavaScript 中,函数内部的值
this
取决于函数的调用方式。 - 当函数作为对象的方法被调用时,
this
指的是调用该方法的对象。 - 当单独调用函数时,
this
指的是全局对象(位于window
浏览器环境中)。 - 在
debounce
函数中,_this
用于存储对函数执行this
时的值的引用。
arguments
目的:
- 该
arguments
对象是一个类似数组的对象,它保存传递给函数的所有参数。 - 在
debounce
函数中,arguments
用于捕获传递给去抖函数的所有参数 (fn
)。这是因为该debounce
函数被设计为通用的,并且可以与任何函数和任意数量的参数一起使用。 - 该行创建对函数内部对象的
let args = arguments
引用。arguments debounce
let args = arguments
:此行捕获传递给去抖函数的所有参数并将它们存储在args
变量中。let _this = this
:该行捕获函数执行时的值debounce
并将其存储在_this
变量中。稍后在回调中使用它setTimeout
。fn.call(_this, ...args)
:此行使用捕获的上下文 ( )_this
和参数 ( )调用原始函数(...args
)。该call
方法用于this
在调用函数时显式设置 的值。
限制流节(Throttting)
节流是指在一定的时间间隔内,无论事件触发频率多高,只执行一次。节流的目的是控制函数的执行频率,保证在指定的时间间隔内不会执行超过一次。典型的应用场景包括处理滚动事件、窗口调整事件等。
实现节流的方法可以是计时器,也可以是记录上一次执行的时间。每次事件触发时,都会检查离开上一次执行的时间是否超过指定的时间间隔,如果是,则执行相应的操作,并更新上一次执行的计时器。
我们可以用一句话来概括一下,帮助小伙伴们更好的理解:在规定的时间内只执行一次
节流的应用:
- 滚动事件(Scrolling): 当用户滚动页面时,滚动事件可能被间隔触发。使用节流可以保证滚动事件处理函数只在一定的时间间隔内执行一次,避免过多的计算和渲染,提高页面性能。
- 窗口调整事件(Resize): 窗口调整事件在用户调整器窗口大小时触发,也可以间隙触发。通过节流,可以保证窗口调整事件处理功能不会过度间隙地执行。
- 输入框输入事件(Input): 处理用户在输入框输入文字时,输入事件会关闭触发。通过节流,可以延迟执行处理函数,避免在每次输入时都立即进行相关操作,比如搜索建议的显示。
- 按钮点击事件(Click): 当用户快速点击按钮时,点击事件可能被间隔触发。通过节流,可以保证按钮点击事件处理函数在一定的时间内间隔内只执行一次,防止过快的连续点击。
而这个例子中我们可以使用节流,当用户快速点击按钮时,点击事件可能被间隔触发。通过节流,可以保证按钮点击事件处理函数在一定的时间内间隔内只执行一次,防止过快的连续点击。
<button id="btn">提交</button> <script> let btn = document.getElementById('btn') function send(){ console.log('提交了'); } btn.addEventListener('click',throttle(send, 1000) ) function throttle(fn, delay){ let prevTime = Date.now() return function(){ if(Date.now() - prevTime > delay){ fn() prevTime = Date.now() } } } </script>
代码中包含了一个闭包,闭包是指在一个函数内部创建另一个函数,并且内部函数可以访问外部函数的变量,当内部函数被返回到外部函数之外时,即使外部函数执行结束了, 但是内部函数引用了外部函数的变量,那么这些变量依旧会被保存在内层中。这里,函数就是一个闭包throttle
。
闭包是一个十分重要的概念,如果小伙伴不太理解闭包的话,可以看我的文章,这篇文章对闭包有着详细的解释: # 前端面试:聊聊闭包 一盏茶的功夫让你彻底掌握闭包!
function throttle(fn, delay) { // 用于存储上一次函数执行的时间戳 let prevTime = Date.now(); // 返回一个新的函数,该函数会进行节流控制 return function() { // 当前时间戳 let currentTime = Date.now(); // 判断是否超过了指定的时间间隔 if (currentTime - prevTime > delay) { // 如果超过了时间间隔,执行传入的函数 fn fn(); // 更新上一次函数执行的时间戳 prevTime = Date.now(); } }; }
let prevTime = Date.now();
: 在节流函数中,使用一个变量prevTime
来存储上一次函数执行的时间戳。初始时,将其设置为当前时间。return function() { ... }
: 节流函数返回一个新的函数,这个函数会进行节流控制。let currentTime = Date.now();
: 在新返回的函数中,获取当前时间戳。if (currentTime - prevTime > delay) { ... }
: 判断当前时间与上一次函数执行的时间间隔是否超过了指定的delay
。如果超过了,就执行传入的函数fn
。fn();
: 如果时间间隔超过了指定的delay
,则执行传入的函数fn
。prevTime = Date.now();
: 更新上一次函数执行的时间戳,以便下一次判断是否超过时间间隔。
这样,通过使用这个节流函数,可以确保在调用返回的函数时,传入的函数 fn
在指定的时间间隔内最多执行一次。这有助于控制频繁触发的事件,以提高性能和用户体验。
当然,我们也需要可以接受send
传入的参数,和send
中的this
关键字指向正确:
<button id="btn">提交</button> <script> let btn = document.getElementById('btn') function send(){ console.log('提交了'); } btn.addEventListener('click',throttle(send, 1000) ) function throttle(fn, delay){ let prevTime = Date.now() return function(){ if(Date.now() - prevTime > delay){ fn.apply(this, arguments) prevTime = Date.now() } } } </script>
今天的内容就到这啦,如果你觉得小编写的还不错的话,或者对你有所启发,请给小编一个辛苦的赞吧