10分钟搞懂防抖与节流:2个函数就能优化你的网站性能,小白都能学会
引言
常见的性能优化场景
优秀的你可能会在网站中添加这些技术:
或者,你又在防守这些奇葩用户压力服务器:
- 频繁点击:按钮点击次数过多,导致频繁提交请求。
这些操作是否听起来很熟悉?
那么,为什么需要对这些场景进行性能优化呢?
为什么需要性能优化?
以现在最常见的 “推荐式搜索框” 为例:

这样的 “推荐式搜索框” 一般都是通过 keyup
事件(键盘按键抬起就触发)监听用户的输入行为,实时根据用户输入的内容从服务器调取符合条件的“推荐值”。
想象一下,如果搜索框每输入一个字符都立即触发搜索请求,会发生什么?
当你快速输入时,系统会接收到大量的请求,服务器的压力会急剧增加。
如果在百度这样的大厂上班这样写代码…… 恐怕直接把服务器给干冒烟了吧。
同样的道理:
- 如果页面在滚动时不断加载内容,浏览器会变得很卡顿,用户的浏览体验会变差。
- 频繁调整窗口大小时,页面布局不断重绘,用户可能会看到不稳定的界面。
这些问题不仅会消耗系统资源,还会让用户感到不适。
那么,如何解决这些问题呢?
从何下手进行优化?
思考一下,我们可以从哪些方面进行性能优化?
我们可以考虑减少不必要的请求,让系统在适当的时候才执行任务。
这时候,防抖和节流就派上用场了。
它们都是基于 设定“响应频率” 削减不必要的请求,以在不影响用户体验的同时减轻服务器压力。
什么是防抖?
想象一下你在打字,每次敲击键盘后,系统都会保存你的文档。如果你打字速度很快,系统就会不停地进行保存操作,导致文档保存系统变得非常忙碌,甚至可能影响你正常打字的流畅度。
防抖的作用就像是为你配备了一位聪明的秘书,她会等你停止打字一段时间后,才一次性保存文档。这样一来,系统就不会被频繁的保存操作打扰,性能得到了优化。
防抖的应用场景
- 搜索框输入: 在搜索框中输入文字时,如果每输入一个字符都进行一次搜索,系统会收到大量的请求。防抖可以在用户停止输入一段时间后再进行搜索,这样就可以避免不必要的请求,减轻服务器的压力。
- 表单提交: 用户快速点击提交按钮多次,会导致表单被多次提交。通过防抖,可以确保表单只在用户停止点击一定时间后提交一次,防止重复提交。
- 窗口大小调整: 当用户调整浏览器窗口大小时,页面布局需要重新计算。如果每次调整都触发重新计算,性能会受到影响。使用防抖,可以等用户停止调整一段时间后再进行布局计算,保证页面的流畅度。
防抖的实现思想
防抖的原理其实很简单:它会在某个事件被频繁触发时,只执行最后一次操作。具体来说,当事件被触发时,系统会设置一个延迟时间(比如500毫秒)。如果在这个延迟时间内事件再次被触发,延迟计时器会重置,直到没有新的触发事件,系统才会执行操作。
防抖功能函数
让我们看看防抖是如何在代码中实现的:
/**
* 防抖函数
* @param {Function} fn 要执行的函数
* @param {Number} delay 延迟时间
* @returns {Function} 返回一个函数
**/
function debounce(fn, delay) {
let timer = null;
return function() { // 闭包
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arguments); // apply()方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数
}, delay);
}
}
这是一个防抖功能函数 debounce
,其接收两个参数:
- 要执行的函数
func
- 执行延迟时间
delay
当返回的新函数被频繁调用时,它会在每次调用时重置延迟计时器。
只有在最后一次调用后经过指定的延迟时间,func
才会被执行。
在防抖函数中,我们使用闭包来记住计时器变量,从而控制函数的执行时机。
在实现的过程中,还有以下细节值得注意
- 计时器变量
timer
:由于timer
变量在debounce
函数的词法作用域内声明,所以它可以被return
的内部函数访问和操作,即使debounce
函数已经执行完毕。 - 闭包的作用:使得每次调用返回的内部函数时,
timer
变量仍然保存在其词法作用域中,从而确保clearTimeout
和setTimeout
可以正常工作。
什么是节流?
为了更好地理解节流 (Throttling),我们可以用一个简单的类比来说明。
通俗解释节流
想象一下你家里的水龙头,如果水流过大,你会把它调小一些,让水以固定的频率流出。
节流的作用类似,它限制某个操作在一定时间内的触发次数。
无论你操作多频繁,节流都会按设定的频率来执行任务,从而防止系统资源被过度消耗。
节流的应用场景
- 滚动加载: 当用户滚动页面时,实时加载新的内容。如果每次滚动都触发加载操作,系统会变得非常忙碌,页面可能会卡顿。节流可以限制加载操作的频率,确保页面流畅运行。
- 窗口大小调整: 当用户调整浏览器窗口大小时,页面布局需要重新计算。如果每次调整都触发重新计算,性能会受到影响。使用节流,可以限制布局计算的频率,确保调整窗口时的流畅度。
- 鼠标移动事件: 在某些交互中,鼠标移动事件会频繁触发。如果每次移动都进行复杂计算,系统资源会被大量占用。节流可以限制这些计算的频率,减少资源消耗。
节流的实现思想
节流的原理是通过设置一个固定的时间间隔,只允许操作在这个间隔内执行一次。
这样,即使事件频繁触发,也不会频繁执���操作,而是按设定的频率执行任务。
节流功能函数
节流功能函数实现如下:
/**
* 节流功能函数
* @param {Function} fn 要执行的函数
* @param {Number} delay 延迟时间
**/
const throttle = (fn, delay) => {
let last = 0; // 上一次执行时间
let deferTimer = null; // 定时器
return (args) => {
let now = +new Date(); // 当前时间;'+' 隐式类型转换,将字符串转换为数字
/**
* 判断是否不是第一次执行 且 再次执行小于延迟时间
* - 是:清除定时器,重新设置定时器
* - 否:更新上一次执行时间,执行函数
**/
if(last && now < last + delay) {
clearTimeout(deferTimer); // 清除定时器
deferTimer = setTimeout(() => { // 重新设置定时器
last = now;
fn(args);
}, delay);
} else {
last = now;
fn(args);
}
}
}
throttle
函数接收两个参数:
- 要执行的函数
func
- 每隔多久执行一次
limit
当返回的新函数被频繁调用时,它会确保 func
按照设定的时间间隔执行,从而控制操作的频率。
在实现的过程中,还有以下细节值得注意
-
上次执行时间
lastRan
和计时器lastFunc
:由于这两个变量在throttle
函数的词法作用域内声明,所以它们可以被返回的内部函数访问和操作。 -
闭包的作用:使得每次调用返回的内部函数时,这些变量仍然保存在其词法作用域中,从而控制函数的调用频率。
防抖与节流的直观对比
-
防抖 (Debouncing) :
- 工作原理:在事件频繁触发时,防抖会延迟事件的执行,直到事件停止触发一定时间后才执行操作。
- 应用场景:适用于只需要在操作结束后执行一次的情况,如搜索框输入、表单提交。
-
节流 (Throttling) :
- 工作原理:在事件频繁触发时,节流会按照设定的时间间隔执行操作,而不管事件是否还在继续触发。
- 应用场景:适用于需要定期执行操作的情况,如滚动加载、窗口大小调整、鼠标移动事件。
转载自:https://juejin.cn/post/7387029190620332067