引言
在前端开发中,我们常常需要处理用户频繁触发的事件,这时防抖与节流就显得尤为重要。它们不仅能显著提升页面的性能,还能优化用户体验。当然,这也是面试过程中面试常考的一个问题,然而,许多人对这两个概念的理解还不够深入,容易混淆它们的使用场景。这次呆同学将详细解析防抖与节流的原理,手写实现,并结合实际应用场景,帮助你在面试中拿捏面试官。
防抖(Debounce)
-
定义:防抖是一种在事件频繁触发时,确保只有最后一次触发的机制。在设置的延迟时间内,如果该事件再次被触发,计时器将重新开始计时,只有在这段时间内不再触发该事件时,才会执行最终的事件处理逻辑。
-
适用场景:
- 输入框实时搜索:防止用户在快速输入时频繁发送请求,只在用户停止输入后一段时间才发送最终请求。
- 窗口大小调整:在用户调整窗口大小时避免过多的计算和渲染,只在调整结束时执行最终的操作。
- 表单输入验证:防抖可以用来在用户停止输入一段时间后进行输入验证。这样可以避免每次输入时都触发验证,减少计算和网络请求。
- 用户行为分析:防抖可以用于分析用户的行为模式,例如在用户最后一次点击后进行分析,而不是在每次点击时都进行处理
节流(Throttle)
-
定义:节流是一种控制事件触发频率的机制。无论事件触发多频繁,在设定的时间间隔内只允许一次事件处理。这意味着在指定时间间隔内,即使事件多次触发,也只会执行一次。
-
适用场景:
- 滚动事件:在用户滚动页面时限制触发事件的频率,避免频繁的 DOM 操作,提升性能。
- 窗口调整大小:在窗口调整大小的过程中限制计算或重新渲染的频率,减少不必要的资源消耗。
- 实时数据更新:节流可以用来在滚动、缩放等操作中定期更新实时数据。例如,在用户滚动页面时,节流可以限制数据更新的频率,防止频繁触发导致性能问题。
- 动画效果:节流可以应用于动画效果中,限制动画的更新频率,从而优化性能并确保平滑的动画体验。
- 频繁的用户操作:节流适用于需要持续响应用户操作的场景,如滚动加载、调整窗口大小等,确保在规定的时间间隔内执行操作。
防抖的实现
防抖函数用于控制事件的频繁触发,确保在一定时间内只有最后一次触发的操作会被执行。比如在用户点击按钮时,只有用户停止点击一段时间后,才会触发点击请求。
手写防抖函数
let btn = document.getElementById('btn');
btn.addEventListener('click', debounce(handle));
function handle(e) {
console.log('点击触发', this, e);
}
// 防抖
function debounce(fn) {
// 不能放在return中,因为这样的话每次调用都会重新创建一个time
let timer = null;
return function(...args) {
let _this = this; // 保存当前的 this
clearTimeout(timer);
time = setTimeout(function() {
fn.apply(_this, args); // 使用保存的 this 调用 fn
}, 1000);
}
}
这里我们通过给定时器取名为timer
,是为了方便清除定时器,因为防抖就是在规定时间内如果用户继续操作,就需要重启定时器,那么在这之前就必须要清除上一个定时器。
补充:由于
setTimout
定时器函数中的this是指向全局的,且接收的回调函数格式是普通函数(如上述中所示),那么执行上下文中的this
会丢失,所以需要先获取到上下文的this
,然后使用保存的this
调用fn
;那么还有一种办法可以省略这一步骤,那便是将回调函数写成箭头函数,因为箭头函数中不存在
this
,如果箭头函数中写了this
,那它代表的是箭头函数外层的函数的执行上下文,详细解释可以看我的这篇文章:this关键字
节流的实现
节流函数用于限制函数的执行频率。在规定的时间间隔内,函数只能被执行一次。例如,在用户连续点击时,限制点击事件的处理频率,以减少计算量。
手写节流函数
let btn = document.getElementById('btn');
btn.addEventListener('click', throttle(handle));
function handle(e) {
console.log('点击触发', this, e);
}
// 节流
function throttle(fn) {
let preTime = Data.now();
return function(...args) {
let curTime = Date.now();
if (curTime - preTime >= 1000) { // 检查时间间隔
preTime = curTime; // 更新上次执行时间
fn.apply(this, args); // 使用当前的 this 调用 fn
}
}
}
这里通过Data
函数来获取时间戳,通过判断用户操作的时间间隔是否大于规定时间,如果大于规定时间就执行函数,因为节流的目的就是在规定的时间间隔内,函数只能被执行一次。
防抖和节流这两种技术,它们其实可以在更复杂的场景中发挥作用,并且在某些情况下可以配合使用。
防抖与节流的配合使用
在某些复杂的场景中,防抖和节流可以配合使用,以获得更好的性能和用户体验。例如:
-
复杂表单输入:
当处理复杂表单输入时,可以使用防抖来处理输入验证或自动保存,避免每次输入时都进行处理。与此同时,可以使用节流来限制数据的提交频率,以防止过于频繁的提交请求。
-
滚动事件与数据加载:
在实现无限滚动时,可以使用节流来限制滚动事件处理的频率,同时使用防抖来处理滚动停止后的数据加载或其他处理。
-
调整窗口大小和复杂布局:
对于需要根据窗口大小调整布局的场景,可以使用节流来控制调整大小事件的处理频率,同时使用防抖来触发布局重新计算或重绘操作。
// 结合防抖与节流的示例
function combinedDebounceThrottle(fn, debounceDelay = 300, throttleDelay = 1000) {
let debounceTimer = null;
let preThrottleTime = 0;
return function(...args) {
const curThrottleTime = Date.now();
// 防抖逻辑
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
if (curTime - preThrottleTime >= throttleDelay) {
fn.apply(this, args);
preThrottleTime = curThrottleTime;
}
}, debounceDelay);
};
}
// 使用示例
const handleScroll = combinedDebounceThrottle(() => {
console.log('处理滚动事件');
}, 300, 1000);
window.addEventListener('scroll', handleScroll);
Lodash
在手写了防抖节流函数之后,我们对这两种技术有了比较深刻的理解,那么我们接下来还需要了解一种比较实用的工具库,它就叫做 Lodash。
Lodash 是一个非常流行的 JavaScript 工具库,其中包含了 debounce
和 throttle
方法,可以方便地实现防抖和节流。这些方法在处理高频事件时非常实用,而且它们的实现已经过了大量的优化,具有很好的性能和兼容性。
使用 Lodash 实现防抖
// 引入 lodash
import _ from 'lodash';
// 使用 lodash 的 debounce 方法
const handleDebounced = _.debounce(function() {
console.log('防抖触发');
}, 1000);
// 绑定事件
button.addEventListener('click', handleDebounced);
使用 Lodash 实现节流
// 使用 lodash 的 throttle 方法
const handleThrottled = _.throttle(function() {
console.log('节流触发');
}, 1000);
// 绑定事件
window.addEventListener('scroll', handleThrottled);
Lodash 导入方法
1. 通过 ES6 模块导入
如果使用的是现代前端构建工具(如 Webpack、Vite 等),可以使用 ES6 模块导入:
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
const handleDebounced = debounce(() => {
console.log('防抖触发');
}, 1000);
const handleThrottled = throttle(() => {
console.log('节流触发');
}, 1000);
2. 通过 Lodash 的完整包导入
如果你想导入整个 Lodash 库,可以这样做:
import _ from 'lodash';
const handleDebounced = _.debounce(() => {
console.log('防抖触发');
}, 1000);
const handleThrottled = _.throttle(() => {
console.log('节流触发');
}, 1000);
3. 通过 CDN 直接引入
如果你在没有构建工具的环境下使用,可以通过 CDN 直接引入:
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
<script>
const handleDebounced = _.debounce(() => {
console.log('防抖触发');
}, 1000);
const handleThrottled = _.throttle(() => {
console.log('节流触发');
}, 1000);
document.getElementById('button').addEventListener('click', handleDebounced);
window.addEventListener('scroll', handleThrottled);
</script>
4. 通过 Node.js 环境下的 CommonJS 模块引入
在 Node.js 或者使用 CommonJS 模块的环境中,可以这样引入:
const debounce = require('lodash/debounce');
const throttle = require('lodash/throttle');
const handleDebounced = debounce(() => {
console.log('防抖触发');
}, 1000);
const handleThrottled = throttle(() => {
console.log('节流触发');
}, 1000);
总结
今天我们深入探讨了防抖(Debounce)和节流(Throttle)这两种高频事件处理技术。防抖主要用于避免频繁触发的事件在短时间内多次执行,它确保只有在事件停止触发一段时间后才会执行指定的操作。节流则限制在一定时间内事件的执行次数,以控制事件处理的频率,避免在高频触发时对性能造成影响。
我们通过实际代码示例理解了这两种技术的实现原理,并讨论了它们的常见应用场景,如输入框实时搜索、滚动事件处理、窗口调整等。我们还探讨了在复杂场景中如何结合使用防抖与节流,以优化性能和用户体验。
这样你在面试中就不但能自信的手写出防抖节流函数,还能加入自己的理解,跟面试官表达自己的想法。