likes
comments
collection
share

10分钟搞懂防抖与节流:2个函数就能优化你的网站性能,小白都能学会

作者站长头像
站长
· 阅读数 22

引言

常见的性能优化场景

优秀的你可能会在网站中添加这些技术

或者,你又在防守这些奇葩用户压力服务器:

  • 频繁点击:按钮点击次数过多,导致频繁提交请求。

这些操作是否听起来很熟悉?

那么,为什么需要对这些场景进行性能优化呢?

为什么需要性能优化?

以现在最常见的 “推荐式搜索框” 为例:

10分钟搞懂防抖与节流:2个函数就能优化你的网站性能,小白都能学会

这样的 “推荐式搜索框” 一般都是通过 keyup 事件(键盘按键抬起就触发)监听用户的输入行为,实时根据用户输入的内容从服务器调取符合条件的“推荐值”

想象一下,如果搜索框每输入一个字符都立即触发搜索请求,会发生什么?

当你快速输入时,系统会接收到大量的请求,服务器的压力会急剧增加。

如果在百度这样的大厂上班这样写代码…… 恐怕直接把服务器给干冒烟了吧。

同样的道理:

  • 如果页面在滚动时不断加载内容,浏览器会变得很卡顿,用户的浏览体验会变差。
  • 频繁调整窗口大小时,页面布局不断重绘,用户可能会看到不稳定的界面。

这些问题不仅会消耗系统资源,还会让用户感到不适。

那么,如何解决这些问题呢?

从何下手进行优化?

思考一下,我们可以从哪些方面进行性能优化?

我们可以考虑减少不必要的请求,让系统在适当的时候才执行任务。

这时候,防抖节流就派上用场了。

它们都是基于 设定“响应频率” 削减不必要的请求,以在不影响用户体验的同时减轻服务器压力。

什么是防抖?

想象一下你在打字,每次敲击键盘后,系统都会保存你的文档。如果你打字速度很快,系统就会不停地进行保存操作,导致文档保存系统变得非常忙碌,甚至可能影响你正常打字的流畅度。

防抖的作用就像是为你配备了一位聪明的秘书,她会等你停止打字一段时间后,才一次性保存文档。这样一来,系统就不会被频繁的保存操作打扰,性能得到了优化。

防抖的应用场景

  1. 搜索框输入: 在搜索框中输入文字时,如果每输入一个字符都进行一次搜索,系统会收到大量的请求。防抖可以在用户停止输入一段时间后再进行搜索,这样就可以避免不必要的请求,减轻服务器的压力。
  2. 表单提交: 用户快速点击提交按钮多次,会导致表单被多次提交。通过防抖,可以确保表单只在用户停止点击一定时间后提交一次,防止重复提交。
  3. 窗口大小调整: 当用户调整浏览器窗口大小时,页面布局需要重新计算。如果每次调整都触发重新计算,性能会受到影响。使用防抖,可以等用户停止调整一段时间后再进行布局计算,保证页面的流畅度。

防抖的实现思想

防抖的原理其实很简单:它会在某个事件被频繁触发时,只执行最后一次操作。具体来说,当事件被触发时,系统会设置一个延迟时间(比如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 变量仍然保存在其词法作用域中,从而确保 clearTimeoutsetTimeout 可以正常工作。

什么是节流?

为了更好地理解节流 (Throttling),我们可以用一个简单的类比来说明。

通俗解释节流

想象一下你家里的水龙头,如果水流过大,你会把它调小一些,让水以固定的频率流出

节流的作用类似,它限制某个操作在一定时间内的触发次数

无论你操作多频繁,节流都会按设定的频率来执行任务,从而防止系统资源被过度消耗。

节流的应用场景

  1. 滚动加载: 当用户滚动页面时,实时加载新的内容。如果每次滚动都触发加载操作,系统会变得非常忙碌,页面可能会卡顿。节流可以限制加载操作的频率,确保页面流畅运行。
  2. 窗口大小调整: 当用户调整浏览器窗口大小时,页面布局需要重新计算。如果每次调整都触发重新计算,性能会受到影响。使用节流,可以限制布局计算的频率,确保调整窗口时的流畅度。
  3. 鼠标移动事件: 在某些交互中,鼠标移动事件会频繁触发。如果每次移动都进行复杂计算,系统资源会被大量占用。节流可以限制这些计算的频率,减少资源消耗。

节流的实现思想

节流的原理是通过设置一个固定的时间间隔,只允许操作在这个间隔内执行一次

这样,即使事件频繁触发,也不会频繁执���操作,而是按设定的频率执行任务。

节流功能函数

节流功能函数实现如下:

/**
 * 节流功能函数
 * @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
评论
请登录