likes
comments
collection
share

前端性能优化篇:防抖与节流性能优化 防抖(Debounce) 何为防抖?防抖的主要思想是在一系列操作中,只执行最后一次操

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

性能优化是在软件开发过程中一个非常重要的环节,尤其是在Web开发领域,因为用户体验很大程度上取决于页面加载速度和交互响应性。而 防抖(Debounce)和节流(Throttle) 是前端开发中常用的两种优化技术,用于减少事件处理函数的执行频率,从而提高应用的性能并减少不必要的计算开销。今天就让我们来实现一下这两种功能吧。

性能优化

防抖(Debounce)

何为防抖?防抖的主要思想是在一系列操作中,只执行最后一次操作,前提是最后一次操作之后有一段特定的时间没有新的操作发生。如果在这段时间内又有新的操作发生,则重新计时

我们可以想象一下,当我们在浏览器输入内容时会有相关词汇出现在下方;这是因为在我们输入的过程中就会频繁地触发输入框的搜索功能,去为我们拿到相关词汇;这种场景下就需要我们对输入框添加一个防抖的功能

前端性能优化篇:防抖与节流性能优化 防抖(Debounce) 何为防抖?防抖的主要思想是在一系列操作中,只执行最后一次操

那让我们来模拟一下这个过程;并实现防抖的功能。

初始化后端项目

首先,我们需要初始化一个后端项目来存放数据资源;

npm init -y   // 初始化后端

// json-server 是驱动的后端接口 API
npm i json-server   // 安装 json-server

// 启动服务器
json-server --watch db.json

JSON Server

json-server 是一个简单的命令行工具,它可以让你快速地创建一个模拟的REST API服务器,这个服务器会从一个JSON文件读取数据,并且提供CRUD操作的端点。它还可以通过一个 JSON 文件或一个目录来启动一个具有 GET 和 POST 请求能力的 API 服务器

db.json 存放数据资源

{
  "users": [
    {
      "id": "1",
      "name": "kunkun"
    },
    {
      "id": "2",
      "name": "kunfc"
    },
    {
      "id": "3",
      "name": "kfc"
    }
  ],
  "post": [
    {
      "id": "7379599360944177189",
      "title": "什么是拷贝?我:Ctrl + C ...",
      "userId": "1"
    },
    {
      "id": "7370244444282896410",
      "title": "HTML5:从网页到富应用的跨越",
      "userId": "2"
    },
    {
      "id": "7378028569689554985",
      "title": "吴恩达系列之:Prompt Engineering——提示词工程",
      "userId": "3"
    },
    {
      "id": "7377901375001329727",
      "title": "从泡茶艺术到编程智慧:模板方法模式",
      "userId": "4"
    }
  ]
}

Restful

RESTful API 是一种设计风格,它认为一切皆是资源,并且每个资源都应该有一个唯一的 URL,并且应该使用 HTTP 方法(GET, POST, PUT, DELETE 等)来操作这些资源。

如下:

  • 获取所有用户: GET http://localhost:3000/users
  • 获取指定用户: GET http://localhost:3000/users/:id
  • 获取所有文章: GET http://localhost:3000/posts
  • 获取指定文章: GET http://localhost:3000/posts/:id
  • 新建文章: POST http://localhost:3000/posts

前端性能优化篇:防抖与节流性能优化 防抖(Debounce) 何为防抖?防抖的主要思想是在一系列操作中,只执行最后一次操

前端搜索页面

接下来,我们就要去设计一个简单的搜索页面来实现 “猜你想要查找” 的功能;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>
        没有防抖的input 
        <input 
        type="text" 
        id="unDebounce" 
        placeholder="请输入您要搜索的用户名"
        >
    </div>
    <div>
        防抖后的input 
        <input 
        type="text" 
        id="debounce" 
        placeholder="请输入您要搜索的用户名"
        >
    </div>
    <script>
        // 不加防抖的
        const inputa = document.getElementById("unDebounce");
        const inputb = document.getElementById("debounce");

        /**
         * 处理用户名称搜索事件。
         * 该函数在用户在输入框中输入名称时被调用,通过fetch获取所有用户数据,并根据输入值过滤用户。
         * @param {Event} e - 输入事件对象
         */
        function handleNameSearch(e) {
            // 获取输入框的值
            const value = e.target.value;
            // 使用fetch获取'http://localhost:3000/users'的内容,并返回一个Promise
            fetch('http://localhost:3000/users')
            // 当Promise完成并返回响应对象时,调用此函数,解析响应的JSON数据并返回一个Promise
          .then(res => res.json())
            // 当 Promise 完成并返回用户数据时,调用此函数
          .then(data => {
                // 将用户数据赋值给一个名为users的常量
                const users = data;
                // 使用filter方法创建一个新的数组,只包含符合过滤条件的用户
                // filter方法的回调函数返回一个真值,将元素保留在结果数组中,否则返回一个假值
                const filterUsers = users.filter(user => {
                    // 使用includes方法检查用户名称是否包含搜索字符串
                    return user.name.includes(value)
                    // 使用字符串的indexOf方法检查用户名称是否包含搜索字符串
                    // return user.name.indexOf(value)!== -1
                })
                // 打印过滤后的用户数组到控制台
                console.log(filterUsers);  
            })
        }  

        inputa.addEventListener('keyup',handleNameSearch)
        inputb.addEventListener('keyup', debounceNameSearch)
    </script>
</body>
</html>

在上面的代码中,handleNameSearch 函数通过 fetch 发送 AJAX 请求从服务器获取所有用户数据,并使用 res.json() 方法解析 JSON 数据,将拿到的数据每一项用数组上es6的新方法 filter 去过滤数据,如果后端数据中包含输入框中的值,就将其保留在返回的新数组中;要注意 filter 方法的回调函数返回一个真值,将元素保留在结果数组中,否则返回一个假值,所以回调函数必须要返回的是布尔型的值

前端性能优化篇:防抖与节流性能优化 防抖(Debounce) 何为防抖?防抖的主要思想是在一系列操作中,只执行最后一次操

这样我们就实现了一个 “猜你想要查找” 的功能;但此时没有添加防抖,每次输入值都会频繁的触发函数handleNameSearch的调用

事件监听与 AJAX 请求

当用户在输入框中键入文本时,通常会触发 keyup 事件。如果不加以控制,每次 keyup 事件都会触发 AJAX 请求来获取建议列表,这可能导致大量的无谓请求。为了减轻服务器压力并提高用户体验,我们可以使用防抖来限制请求的频率。

实现防抖

在用户频繁触发某个事件时(如键盘输入或窗口大小改变),防抖可以确保只执行最后一次操作,前提是最后一次操作之后有一段特定的时间没有新的操作发生。那我们要如何实现防抖呢?

// 闭包功能函数
function debounce(func, delay) {
    // 返回值必须得是函数  keyup 事件处理函数
    // let id;
    return function(args){
        clearTimeout(func.id)
        // 函数是对象,id 挂在func上 func 是闭包中的自由变量
        func.id = setTimeout(function(){
            func(args)
        }, delay)
    }
}
// 
const debounceNameSearch = debounce(handleNameSearch, 500)

在上面的代码中,关于防抖函数的实现如下;

  • 闭包debounce 函数使用闭包来保存定时器的 id 变量,这样每次keyup事件的触发,调用 debounce 返回的新函数时,都会记录设置的定时器的 id 在闭包之中
  • 抽象debounce 接收一个函数 func 和一个延迟时间 delay,然后返回一个新的函数,这个新函数会在用户停止输入一定时间后执行 func
  • 定时器setTimeout 用于延迟执行函数,而 clearTimeout 用于取消尚未执行的定时器;每次输入触发keyup事件时,都会通过clearTimeout去清除上一次设置的定时器重置延迟时间delay
  • 最后执行: 当用户停止输入并且过了 delay 时间后,func 会被执行。

前端性能优化篇:防抖与节流性能优化 防抖(Debounce) 何为防抖?防抖的主要思想是在一系列操作中,只执行最后一次操

可以看到,添加了防抖功能后,在我们输入内容时,并不会触发搜索函数handleNameSearch的调用,而会在我们停止输入后,过了延迟时间后才执行搜索函数。

节流(Throttle)

节流(throttle) 是一种与防抖类似的技术,用于限制函数的执行频率。与防抖不同的是,节流确保函数按照固定的时间间隔执行,即使在这段时间内该事件被触发多次。换句话说,无论事件触发多少次,节流保证函数至少每隔一段时间执行一次

实现节流

清楚节流的原理后,我们又该怎样实现节流呢?

<!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>
    <div class="row">
        <div>
            没有节流的input <input type="text" id="inputa">
        </div>
        <div>
            节流后的input <input type="text" id="inputc">
        </div>
    </div>
    <script>
        const inputa = document.getElementById('inputa')
        const inputc = document.getElementById('inputc')

        // 耗时耗性能任务 => promise 
        const ajax = (content) => {
            console.log(`ajax request ${content}`);
            
        }

        // 节流功能
        // 定义的时刻
        const throttle = (func, delay) => {
            // last 上一次是啥时候执行的
            // deferTimer 定时器id
            let last, deferTimer // 自由变量
            // 才是事件的处理函数
            // 定义时,生成时 func delay
            // keyup return func 调用时能找到闭包中的自由变量
            return (args) => {
                // 当前时间,隐式类型转换
                let now = +new Date()
                if (last && now < last + delay) {
                    // 触发干掉
                    clearTimeout(deferTimer)
                    // 让最后一次输入后,过delay后也能执行一次
                    deferTimer = setTimeout(()=>{
                        last = now
                        func(args)
                    }, delay)
                }else{
                    last = now // 第一次时间
                    func(args) // 先执行一次
                }
            }
        }

        inputa.addEventListener('keyup', (e)=>{
            ajax(e.target.value)
        })
        // 生成的时候
        let throttledFunc = throttle(ajax, 1000) // 给事件绑定节流功能

        inputc.addEventListener('keyup', (e)=>{
            let value = e.target.value
            // googleSuggest 体验,1000
            // qps
            
            throttledFunc(value) // 节流函数的执行
        })

    </script>
</body>
</html>

关于节流函数的功能实现,throttle 函数接收了两个参数,需要添加节流的函数 func 和 函数需要触发的时间间隔 delay ;我们使用闭包的方式存储了两个自由变量, 上一次执行函数的时间戳 last 和 一个在延迟时间过后执行的定时器 deferTimer

  • 第一次触发:如果last等于0,说明是第一次触发,直接执行函数并更新last
  • 在等待时间之外:如果当前时间减去last大于等待时间delay,则执行函数并更新last
  • 在等待时间内:如果当前时间减去last小于等待时间delay,则清除之前的定时器并设置一个新的定时器来延迟执行函数;这可以确保用户在最后一次输入后,也能够在等待时间delay过后,再次执行函数。

前端性能优化篇:防抖与节流性能优化 防抖(Debounce) 何为防抖?防抖的主要思想是在一系列操作中,只执行最后一次操

可以看到,添加了节流后,尽管输入框正在输入值,函数也会在每个时间间隔过后执行一次。

小结

防抖(debounce)和节流(throttle)是两种常见的前端技术,它们都用于限制函数的执行频率,从而提高应用的性能和响应性。防抖是指在一系列连续的触发事件中,只有在最后一次事件发生之后的一段时间内没有新的触发,才会执行函数,而节流是在一定的时间间隔内只允许执行一次函数。

转载自:https://juejin.cn/post/7400242491779383330
评论
请登录