一篇文章带你彻底认识「防抖和节流」防抖和节流是什么
防抖和节流是编程中常用的两种技术,用于控制函数执行的频率,防抖(Debounce)是指在一个事件触发后,规定一个时间间隔,如果在这个间隔内事件再次被触发,则重新计算时间间隔,直到最后一次事件触发后,才执行函数,而节流(Throttle)则是指限制一个函数在一定时间内只能执行一次,两者都可以有效防止函数频繁执行导致的性能问题,在实际应用中,防抖常用于搜索框、按钮点击等场景,而节流则常用于滚动加载、窗口调整大小等场景,通过合理使用防抖和节流,可以显著提升程序的性能和用户体验。
一篇文章带你彻底认识「防抖和节流」
在现代前端开发中,性能优化是一个永恒的话题,为了提高用户体验,减少不必要的资源消耗,开发者们需要掌握各种优化技巧。“防抖”(Debounce)和“节流”(Throttle)是两种常用的性能优化技术,它们通过控制函数执行的频率,来减少高频事件对系统资源的消耗,本文将带你彻底认识这两种技术,理解它们的原理、应用场景以及实现方法。
防抖(Debounce)
1 什么是防抖?
防抖,顾名思义,就是防止某个函数在短时间内的频繁执行,当一个事件(如键盘输入、窗口大小调整等)频繁触发时,防抖会限制该函数在一定时间间隔内只执行一次,这样可以有效减少函数执行的次数,从而节省资源。
2 防抖的应用场景
防抖广泛应用于需要处理高频事件的场景,如:
- 搜索框输入:用户输入时,如果防抖时间设置为300毫秒,那么在用户停止输入后的300毫秒内,搜索建议或自动补全功能才会执行一次请求。
- 窗口大小调整:当浏览器窗口大小频繁变化时,防抖可以确保调整布局的函数不会频繁执行。
- 滚动加载:在滚动事件中,防抖可以确保只有在用户停止滚动后的一段时间内才执行加载更多数据的操作。
3 防抖的实现方法
防抖的实现通常依赖于定时器(setTimeout
),以下是一个简单的防抖函数实现:
function debounce(func, wait) { let timeout; return function() { const context = this; const args = arguments; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; }
在这个实现中,debounce
函数接受两个参数:func
是要防抖的函数,wait
是等待时间(以毫秒为单位),每次调用返回的函数时,都会清除之前的定时器并重新设置一个新的定时器,只有在等待时间结束后,目标函数才会执行。
节流(Throttle)
1 什么是节流?
与防抖不同,节流是控制函数在一定时间内只能执行一次,也就是说,无论事件触发多少次,函数在指定时间内只会被执行一次,节流适用于需要限制函数执行频率的场景。
2 节流的应用场景
节流的典型应用场景包括:
- 滚动加载:在滚动事件中,节流可以确保在指定的时间间隔内只执行一次加载操作,每秒钟加载一次新的内容。
- 窗口大小调整:当浏览器窗口大小变化时,节流可以确保调整布局的函数在一段时间内只执行一次。
- 鼠标移动:在鼠标移动事件中,节流可以防止函数频繁执行,从而优化性能,每500毫秒记录一次鼠标的位置。
3 节流的实现方法
节流的实现同样依赖于定时器(setTimeout
),以下是一个简单的节流函数实现:
function throttle(func, limit) { let lastFunc; let lastFuncTime; return function() { const context = this; const args = arguments; if (!lastFuncTime) { func.apply(context, args); lastFunc = func; } else { clearTimeout(lastFuncTime); lastFuncTime = setTimeout(function() { if (lastFunc === func) { // Check if it was not replaced by a new throttled function call during wait time. lastFunc.apply(context, args); // This will be the throttled call. } else { // Cleanup timer if the throttled function was replaced. clearTimeout(lastFuncTime); // Cancel the timer if the throttled function was replaced. This can happen if the throttled function is called multiple times within the wait period. } }, limit); } }; }
在这个实现中,throttle
函数接受两个参数:func
是要节流的函数,limit
是时间间隔(以毫秒为单位),每次调用返回的函数时,都会检查上次执行时间是否超过时间间隔,如果超过时间间隔,则执行目标函数并重新设置下次执行时间;否则,会清除之前的定时器并重新设置一个新的定时器,这样可以确保目标函数在指定时间内只执行一次。 需要注意的是,这个实现中使用了闭包来保存上次执行的函数和时间,如果目标函数在指定的时间间隔内被替换为另一个节流函数,则之前的定时器会被取消,这是为了确保只有最新的节流函数会在时间间隔结束时执行,在某些情况下(例如当目标函数被替换为另一个相同间隔的节流函数时),这种清理可能是不必要的或甚至是有害的,因此在实际应用中需要根据具体需求进行调整和优化。 需要注意的是这个实现中有一个小问题:如果在 limit
时间间隔内连续多次调用 throttle
函数(即创建新的节流函数),那么只有最后一个节流函数的定时器会被保留下来并触发执行,这通常不是预期的行为;通常我们期望每个独立的 throttle
函数调用都能在其自己的时间间隔后触发执行其对应的函数(即每个 throttle
函数实例都应该有自己独立的定时器),为了解决这个问题可以在每次创建新的 throttle
函数实例时重置一个唯一的标识符来区分不同的实例并相应地管理它们的定时器(例如使用 WeakMap
或其他数据结构来存储这些定时器和它们对应的唯一标识符),但这里为了简化示例并没有展示这种更复杂的实现方式;在实际应用中需要根据具体需求进行适当修改和优化以符合期望的行为和性能要求。 除此之外这个实现还考虑了如果目标函数在定时器触发之前被替换为另一个相同间隔的节流函数调用时应该取消之前的定时器以避免重复执行旧版本的目标函数(即“去抖动”效果),然而这种去抖动效果并不是所有情况下都是必要的;在某些情况下可能希望即使目标函数被替换也能保留之前的定时器并触发执行旧版本的目标函数(即“保持旧状态”效果),因此在实际应用中需要根据具体需求进行适当配置和调整以符合期望的行为和性能要求。 在实际应用中还可以考虑使用现成的库如 lodash 的 _.throttle
函数来简化实现和避免上述复杂情况;这些库通常提供了更健壮和灵活的节流功能以及更好的性能和兼容性支持,但无论使用哪种方法都需要仔细考虑其应用场景和性能影响以确保优化效果符合预期并避免引入新的问题或漏洞。