前端性能优化的关键策略:防抖节流

前言

在前端开发中,性能优化是一个永恒的话题。本篇文章聚焦对于高频触发的事件处理,如窗口大小调整、滚动、键盘输入等,这些事件若未经妥善处理,不仅会导致不必要的资源消耗,还可能引发页面的卡顿、响应迟缓,非常影响用户的使用体验。为了解决这些问题,两种常用的技术策略应运而生:防抖(Debouncing)和节流(Throttling),这两种技术各有特点,适用于不同的场景,但都对于前端性能优化有着重要作用。

防抖

特点

防抖顾名思义就是防止抖动,这个技术的核心思想是在于延迟执行,在规定的时间间隔内,无论事件被触发多少次,都只执行最后一次函数。这种技术特别适用于那些需要等待用户操作完成后再执行的任务,如搜索框的实时查询、表单验证等这种机制,以减少不必要的计算和DOM操作。

应用场景

  • 表单验证:在用户停止输入后进行验证,而不是每输入一个字符就验证一次
  • 按钮点击:防止用户在短时间内多次点击导致的重复提交
  • Ajax 请求:减少因频繁触发而导致的网络请求次数

实现

接下来我将用一个小demo来讲解防抖的整体实现思路:

首先写一个按钮来模拟表单提交事件,我们监听这个提交事件:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>防抖</title>
</head>
<body>
    <button id="btn">提交</button>
    
    <script>
        let btn = document.getElementById('btn')
        function handle(e) {
            // 模拟ajax 请求
            console.log('提交')
        }
        btn.addEventListener('click',handle())
    </script>
</body>
</html>

多次点击提交按钮我们可以在浏览器的开发者工具的控制台中看到多次打印了 "提交",但是按照性能要求表单应该被限制为在一定时间间隔内只执行一次,所以这里我们要控制请求次数来优化性能,也就是说在一定时间内我们点击的提交按钮只在最后一次向浏览器发送请求,这里的防抖逻辑就可以使用定时器来实现。

在防抖函数 debounce 中,我们定义了一个外部函数,它接收一个要执行的函数fn和一个等待时间 wait 作为参数,并返回一个新的函数。这个返回的函数是一个闭包,它内部维护了一个定时器 timer。当这个返回的函数被调用时(即每次点击提交按钮时),如果定时器 timer 已经存在且未到期,则清除它并重新设置。这意味着如果在 wait 时间间隔内再次触发事件,之前的等待就会被取消,并重新开始计时。直到最后一次触发事件后,等待时间 wait 过去,才会执行原始的函数 fn 。

html 复制代码
<script>
btn.addEventListener('click', debounce(handle))

// 防抖函数
function debounce(fn, wait = 3000) {
    let timer = null
    return function() {
        clearTimeout(timer)    // 如果第二次时间没到1s,就销毁上一次的定时器
        timer = setTimeout(() => {
             fn()
        }, wait)
    }
}
</script>

然而,一个完整的防抖函数实现还需要考虑 this上下文和事件参数的传递问题。在原始的事件处理函数handle中,this 指向触发事件的元素 btn ,并且会接收到事件对象 e 作为参数。但是,在防抖函数返回的闭包函数中,由于 setTimeout 的回调默认在全局上下文(浏览器中是window)中执行,this 的指向会变为 window ,且如果没有特别处理,事件对象 e 也无法直接传递给 handle 函数。

为了解决这个问题,我们需要在防抖函数内部捕获this的上下文和事件参数,然后在 setTimeout 的回调中通过显示绑定将它们传递给原始的处理函数 handle 。由于箭头函数不绑定自己的 this ,它会捕获其所在上下文的 this 值,但在这里我们实际上需要在 setTimeout 的回调外部捕获 this ,并在回调内部使用它,因此直接使用箭头函数并不足以解决 this 的传递问题,所以要通过闭包来保存 this 的上下文。 最终,防抖函数就能够确保无论事件被触发多少次,只有在最后一次触发后经过指定等待时间,才以正确的 this 上下文和事件参数执行原始的处理函数。

如果读到这里你还是对this的指向有疑惑,建议可以阅读这篇文章探索 this 的指向之谜先去学习一下this哦 (o∀o)っ

html 复制代码
<script>
// 防抖函数
function debounce(fn, wait = 3000) {
    let timer = null
    return function() {
        clearTimeout(timer)    // 如果第二次时间没到1s,就销毁上一次的定时器
        timer = setTimeout(() => {
             console.log(this);   // this 指向 btn,this是function的
             fn.call(this, e)
        }, wait)
    }
}
</script>

总结一下,实现防抖的过程:

  1. debounce 返回一个函数体,跟debounce形成一个闭包
  2. 子函数体中每次先销毁上一个setTimeout,再重新创建一个setTimeout
  3. 还原 原函数的 this 指向
  4. 还原 原函数的参数

完整代码:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>防抖</title>
</head>
<body>
    <button id="btn">提交</button>

    <script>
        let btn = document.getElementById('btn')

        // 任何事件触发都有事件参数
        function handle(e) {
            // 模拟ajax 请求
            console.log('提交')  
        }
        btn.addEventListener('click', debounce(handle))

        // 防抖函数
        function debounce(fn, wait = 3000) {
            let timer = null
            return function(e) {
                clearTimeout(timer)    // 如果第二次时间没到1s,就销毁上一次的定时器
                timer = setTimeout(() => {
                     fn.call(this, e)
                }, wait)
            }
        }
    </script>
</body>
</html>

节流

特点

节流的技术核心在于频率控制,在一定的时间间隔内,无论事件被触发多少次,函数都只会被执行一次。与防抖不同的是,节流会立即执行第一次触发的函数,并在随后的时间内进行限制。

应用场景

  • 鼠标移动事件:在用户移动鼠标时,限制相关函数的执行频率,避免资源过度消耗。
  • 滚动事件:在用户停止滚动后再执行相关操作,如懒加载图片或计算布局

实现

节流的实现也涉及到定时器的使用,但与防抖相比,其逻辑更加关注于控制执行间隔:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流</title>
    <style>
        #scrollable {
            height: 2000px;
            width: 100px;
            background-color: rgb(241, 174, 174);
            overflow: auto;
        }
    </style>
</head>
<body>
    <div id="scrollable"></div>

    <script>
        function handleScroll(e) {
            console.log('滚动', this);
        }
        window.addEventListener('scroll', throttle(handleScroll));

        // 节流函数
        function throttle(fn, limit = 1000) {
            let inThrottle;
            return function (e) {
                const context = this;
                if (!inThrottle) {
                    fn.call(context, e);
                    inThrottle = true;
                    setTimeout(() => inThrottle = false, limit);
                }
            };
        }
    </script>
</body>
</html>

注意:上述节流实现是 尾端节流 的一个例子,即在首次触发立即执行和指定时间间隔的末尾执行函数。还有一种 前端节流 的实现方式,它会在每个时间间隔的开始时执行函数,具体实现可以根据实际需求选择。

结语

防抖和节流作为前端性能优化的重要手段,通过控制函数执行的频率,有效降低了资源消耗,提升了应用的响应速度和用户体验。希望本文能够帮助你更好地理解这两种技术,感谢观看!`・ω・´)ゞ敬礼っ

相关推荐
Sam90291 分钟前
【Webpack--007】处理其他资源--视频音频
前端·webpack·音视频
Code成立3 分钟前
HTML5精粹练习第1章博客
前端·html·博客·html5
架构师ZYL14 分钟前
node.js+Koa框架+MySQL实现注册登录
前端·javascript·数据库·mysql·node.js
gxhlh1 小时前
React Native防止重复点击
javascript·react native·react.js
一只小白菜~1 小时前
实现实时Web应用,使用AJAX轮询、WebSocket、还是SSE呢??
前端·javascript·websocket·sse·ajax轮询
晓翔仔2 小时前
CORS漏洞及其防御措施:保护Web应用免受攻击
前端·网络安全·渗透测试·cors·漏洞修复·应用安全
jingling5552 小时前
后端开发刷题 | 数字字符串转化成IP地址
java·开发语言·javascript·算法
J老熊2 小时前
Linux下抓包分析Java应用程序HTTP接口调用:基于tcpdump与Wireshark的综合示例
java·linux·运维·web安全·http·面试
GISer_Jing3 小时前
【前后端】大文件切片上传
前端·spring boot
csdn_aspnet3 小时前
npm 安装 与 切换 淘宝镜像
前端·npm·node.js