结合Google Suggestion带你实现防抖节流

前端防抖节流

在现代Web开发中,用户体验和服务器性能是两个非常重要的方面。频繁的用户操作,如输入搜索内容、滚动页面等,可能会导致大量的请求被发送到服务器,从而增加服务器的负担。为了解决这个问题,前端开发中常用防抖(Debounce)技术。本文将详细探讨防抖和节流的概念、实现方式及其在实际开发中的应用。

Google Suggestion

相信大家在日常搜索信息时都体验过当你每输入一个字符,都会弹出相关联的信息。其实用户在输入搜索内容时,前端是会向后端发送请求以获取匹配的数据。然而,如果每次输入一个字符就发送一次请求,会导致服务器压力剧增,更别提每天有成千上万的用户在进行信息搜索。因此,我们需要控制请求的频率,这就引入了防抖的概念。

事件处理与防抖

模拟后端数据

首先我们用json-serve来模拟一下后端数据

Bash 复制代码
npm init -y 
npm install json-server

项目初始化完毕后,在package.json中添加脚本:

json 复制代码
"scripts": {
    "dev": "json-server --watch db.json"
}

然后创建db.json文件:

json 复制代码
{
  "users": [
    {
      "id": "1",
      "name": "jesper"
    },
    {
      "id": "2",
      "name": "kim"
    },
    {
      "id": "3",
      "name": "jimmy"
    }
  ]
}

最后运行json-server

bash 复制代码
npm run dev

这会启动一个本地服务器,例如可以通过http://localhost:3000/users/1访问用户id为1的数据。我这边默认是3000端口,你也可以用--port指定端口。

事件触发与Ajax请求

在用户每次输入一个字符时,都会触发一个keyup事件。所以我们拿到输入框的DOM节点并监听keyup事件,再写一个事件处理函数拿到与输入字符相关的数据

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>
    <div>
        没有防抖的输入框
        <input type="text" id="unDebounce" placeholder="请输入您要搜索的用户名">
    </div>
    <script>
        // 不加防抖的输入框
        const input = document.getElementById('unDebounce');

        function handleNameSearch(e) {
            const value = e.target.value;
            fetch("http://localhost:3000/users")
                .then(res => res.json())
                .then(data => {
                    const users = data;
                    const filterUsers = users.filter(user => {
                        return user.name.includes(value);
                    });
                    console.log(filterUsers);
                });
        }

        input.addEventListener("keyup", handleNameSearch);
    </script>
</body>
</html>

以上就是一个没有防抖的输入框,效果就是每次敲击键盘,前端都会向后端请求数据,性能开销很大。

防抖(Debounce)

防抖是指在连续事件触发后,只在事件结束的一段时间后执行一次回调函数。也就是说,如果在这段时间内事件再次触发,计时器会被重置,直到事件不再触发,才会执行回调函数。

防抖函数实现

以下是一个使用防抖技术优化用户输入搜索的示例:

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>
    <div>
        防抖后的输入框
        <input type="text" id="Debounce" placeholder="请输入您要搜索的用户名">
    </div>
    <script>
        // 加防抖的输入框
        const input2 = document.getElementById('Debounce');

        function handleNameSearch(e) {
            const value = e.target.value;
            fetch("http://localhost:3000/users")
                .then(res => res.json())
                .then(data => {
                    const users = data;
                    const filterUsers = users.filter(user => {
                        return user.name.includes(value);
                    });
                    console.log(filterUsers);
                });
        }

        // 防抖函数
        function debounce(fn, delay) {
            return function(args) {
                clearTimeout(fn.id);
                fn.id = setTimeout(() => {
                    fn(args);
                }, delay);
            }
        }

        const debounceNameSearch = debounce(handleNameSearch, 500);
        input2.addEventListener("keyup", debounceNameSearch);
    </script>
</body>
</html>

防抖原理

防抖函数通过使用定时器来延迟函数的执行。如果在定时器计时结束之前再次触发事件,定时器会被重置。这样可以确保只有在一段时间内没有新的事件触发时,回调函数才会被执行。

在上面的示例中,可以看到主要变化就是我们使用了一个防抖函数debounce来包裹handleNameSearch,并将防抖后的函数绑定到输入框的keyup事件上。这样,只有在用户停止输入500毫秒后,才会发送请求。

所以我们这边主要来解毒一下防抖函数:

  1. 这个防抖函数接收两个参数:fn是需要防抖的函数,delay是延迟时间。
  2. 用一个定时器延缓函数fn的执行,并用fn.id保存setTimeout返回的定时器ID,以便之后可以通过这个ID取消定时器(这里也可以利用闭包)
  3. 如果用户的输入在距上次输入时间小于delay,则会取消上一次的定时器,也就取消了函数的执行
  4. 所以就达成了只有之后没有输入或两次输入超过delay时间才会执行函数的效果

防抖的应用场景

防抖技术在以下场景中非常有用:

  1. 搜索输入框:当用户在搜索框中输入内容时,可以使用防抖技术减少请求次数,降低服务器压力。
  2. 窗口大小调整:当用户调整浏览器窗口大小时,可以使用防抖技术减少resize事件处理函数的调用次数。
  3. 表单验证:在表单输入框中进行实时验证时,可以使用防抖技术减少验证函数的调用次数。

节流(Throttle)

节流是指在连续事件触发时,保证一定时间段内只调用一次回调函数。与防抖不同,节流是在一定时间内周期性地执行回调函数,而不是在事件结束后才执行。

节流函数实现

由上面的防抖可知,防抖节流的主要实现方式就是写一个防抖节流函数,运行结果为返回一个函数,使通过传参进来的函数按照我们自己制定的规则来触发。所以下面我们直接给出节流函数。

以下是一个简单的节流函数示例:

js 复制代码
 const throttle = (func,delay) => {
            //last 上一次是什么时候执行的 deferTimer 定时器id
            let last,deferTimer
            return (args) => {
                //当前时间
                let now = +new Date() 
                if(last && now < last + delay){
                    clearTimeout(deferTimer);   
                    deferTimer = setTimeout(() => {
                        last = now;
                        func(args);
                    },delay)
                }else{
                    last = now
                    func(args)
                }
            }
        }

节流原理

节流函数代码量比防抖多了一些,不着急,我们先来看下面更简单的代码

js 复制代码
const throttle = (func,delay) => {
            //last 上一次是什么时候执行的
            let last
            return (args) => {
                //当前时间
                let now = +new Date() 
                if(last && now < last + delay){
                    
                }else{
                    last = now
                    func(args)
                }
            }
        }

可以看到我们把if中的内容全挖空了,大家觉得上面的代码能实现节流的效果吗?

我们来分析一下:

  1. 首先函数接收两个参数,fn是需要防抖的函数,delay是延迟时间。然后定义了一个变量last用来记录函数上次执行的时间。
  2. 定义了一个变量now来记录当前时间,并用+进行隐式类型转换,将date对象转换为数字类型,便于计算。
  3. 第一次触发,last没有值,进入else执行func并记录执行时间。
  4. 接下来的触发中,不断刷新now值判断现在距离上次执行函数有没有超过时间delay。没有超过则进入if,什么都不做,反之进入else,并重复执行上述步骤。

这么看来,这样不是可以实现吗,但其实我们漏掉了一种特殊情况,就是第一次触发后,现在距离上次执行函数有没有超过delay的时间内用户一直触发,但就在间隔要超过delay的前一刻,用户又不触发了,那结果会是什么,函数不执行,那用户在这段时间的输入不是就无效了吗,所以我们还得保证最后一次要执行。什么?保证最后一次要执行?这不就是防抖吗,所以我们直接把防抖的内容放到节流函数的if中,并给last赋值,就完成了节流函数。

js 复制代码
const throttle = (func,delay) => {
            //last 上一次是什么时候执行的 deferTimer 定时器id
            let last,deferTimer
            return (args) => {
                //当前时间
                let now = +new Date() 
                if(last && now < last + delay){
                    clearTimeout(deferTimer);   
                    deferTimer = setTimeout(() => {
                        last = now;
                        func(args);
                    },delay)
                }else{
                    last = now
                    func(args)
                }
            }
        }

节流的应用场景

  1. 滚动事件(scroll):例如在实现图片懒加载时,监听滚动事件最好加上节流。
  2. 鼠标移动(mousemove) :当用户在页面上移动鼠标时,mousemove 事件会频繁触发。如果每次都执行回调,可能会导致页面性能下降。节流可以控制鼠标事件触发的频率。

总结

  • 防抖适用于需要等待用户操作结束之后才执行某个动作的场景(例如输入框的自动完成、表单验证)。

  • 节流适用于需要限制事件执行频率、减少频繁执行导致性能问题的场景(例如滚动、鼠标移动、API 请求)。

相关推荐
_一条咸鱼_2 分钟前
Android ARouter 基础库模块深度剖析(四)
android·面试·android jetpack
_一条咸鱼_28 分钟前
Android ARouter 核心路由模块原理深度剖析(一)
android·面试·android jetpack
BillKu29 分钟前
Vue3 + TypeScript中provide和inject的用法示例
javascript·vue.js·typescript
_一条咸鱼_34 分钟前
Android ARouter 编译器模块深度剖析(二)
android·面试·android jetpack
培根芝士35 分钟前
Electron打包支持多语言
前端·javascript·electron
Baoing_1 小时前
Next.js项目生成sitemap.xml站点地图
xml·开发语言·javascript
mr_cmx1 小时前
Nodejs数据库单一连接模式和连接池模式的概述及写法
前端·数据库·node.js
东部欧安时1 小时前
研一自救指南 - 07. CSS面向面试学习
前端·css
沉默是金~2 小时前
Vue+Notification 自定义消息通知组件 支持数据分页 实时更新
javascript·vue.js·elementui
涵信2 小时前
第十二节:原理深挖-React Fiber架构核心思想
前端·react.js·架构