当前端开发遇上相声:函数防抖与节流的逗趣演绎

相声

【开场白】 话说从前啊,有个地方叫前端村,村里的程序员们个个都是高手,但最近他们遇到了个难题,那就是用户操作太频繁,服务器累得直喘气。怎么办呢?别急,咱村有两位奇人,一位叫"防抖侠",另一位叫"节流仙"。今天咱们就来讲讲他们如何联手拯救前端村的故事。

【防抖侠登场】 防抖侠,江湖人称"耐心大师",他最擅长的就是让人冷静下来。比如说吧,村里有个搜索框,用户一激动,手指头就跟开了挂似的,键盘啪啪啪地响。这要是没个节制,服务器非得被折腾得七荤八素不可。防抖侠说:"别急,让我来。"他施展绝技"耐心等待",告诉用户:"你先歇会儿,等你手停了500毫秒,我再帮你找你要的东西。"这样一来,用户操作再快,也得等那半秒钟过去,服务器这才松了口气,再也不怕用户的手速了。

【实战演练】 一天,防抖侠路过村口,看到村长正愁眉苦脸地对着电脑。原来,村长正在用一个输入框录入村民信息,可是每按下一个键,电脑就吭哧吭哧地响应,就成下面那样了。

"防抖侠,快救救我!"村长哭诉道。防抖侠微微一笑,掏出他的"防抖秘籍":

javascript 复制代码
// 闭包
        function debounce(func, delay) {
            // 返回值必须得是函数 keyup 事件处理函数
            return function (args) {
                clearTimeout(func.id); // 清除定时器
                // 对象 , id挂载到func对象上 func是闭包中的自由变量
                func.id = setTimeout(() => func(args), delay);
            };
        }

input框就变成这样了

他把这招教给了村长,村长立刻对输入框施了魔法,从此,无论村长怎么狂敲键盘,输入框都稳如泰山,只有在村长真正停下手指,那半秒钟过后,才开始干活,效率嗖嗖地往上窜。

【节流仙出场】 说完防抖侠,咱再聊聊节流仙。节流仙是个守规矩的人,他的信条是"有序才是王道"。他发现,有些用户喜欢玩"疯狂滚动大赛",屏幕上下翻飞,服务器忙得团团转。节流仙说:"不行,得给他们立个规矩。"于是,他施展"定时通行令",告诉用户:"每秒钟只能请求一次,多了不行。"

【实战演练】 节流仙来到村东头的公告栏前,那里贴满了各种通知,村民们经常来来回回查看。但是,每有人经过,公告栏就自动刷新,搞得服务器不堪重负。搞成这样了

"节流仙,你得帮帮我们啊!"村民们围了过来。节流仙点点头,施展"节流神功":

javascript 复制代码
       const throttle = (func, delay) => {
     
            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); 
                }
            }
        }

就变成这样了

他把这招传授给公告栏管理员,管理员一试,果然奏效。现在,无论多少人围观,公告栏都从容不迫,每秒钟只刷新一次,服务器也终于可以喘口气了。

【结尾】 就这样,防抖侠和节流仙联手,用他们的智慧和技巧,解决了前端村的大麻烦。从此,前端村的程序运行得又快又稳,村民们的生活更加美好。记住,无论是防抖还是节流,都是为了让我们的应用更加高效,用户体验更加顺畅。下次你遇到类似的难题,不妨想想防抖侠和节流仙的故事,说不定就能找到解决之道呢!

【谢幕】 好了,各位看官,咱们今天的相声就讲到这里,希望大家喜欢。记住,编程也可以很有趣,就像咱们的防抖侠和节流仙一样,用智慧解决问题,让生活充满欢笑。下次见,拜拜!

在上述故事中,防抖侠与节流仙分别代表了前端开发中两种常用的技术------防抖(Debounce)和节流(Throttle)。这两种技术主要用于优化用户交互和提高系统性能,尤其在处理高频触发事件时特别有效。

正文

防抖(Debounce)

作用: 防抖的主要目的是避免在短时间内连续多次执行同一函数,直到最后一次触发后的一段时间内不再有新的触发,才会执行该函数。它能有效减少不必要的函数调用,减轻服务器负担,提升用户体验。

使用场景:

  • 搜索框输入:当用户在搜索框中输入文本时,每次按键都会触发事件,频繁的请求会增加服务器负载。使用防抖可以让系统在用户停止输入一段时间后再发送请求,避免了不必要的查询。
  • 窗口大小调整:浏览器窗口大小改变时也会触发事件,频繁调整可能导致页面布局频繁重新计算。防抖可以确保在用户完成调整后才重新计算布局,提高性能。

节流(Throttle)

作用: 节流技术限制了函数执行的频率,确保在指定的时间间隔内,函数最多只能执行一次。这有助于控制资源消耗,防止过度使用CPU或网络资源。

使用场景:

  • 滚动监听:当用户快速滚动页面时,每一帧都会触发滚动事件,如果直接响应这些事件,可能会导致页面卡顿。使用节流可以保证在一定时间间隔内只响应一次滚动事件,保持页面流畅。
  • 地图缩放:地图应用中,频繁的缩放操作会触发多次重新渲染,通过节流可以确保在用户完成缩放动作后,才进行一次渲染,提高地图加载速度。

实现方式

防抖和节流的实现通常依赖于setTimeoutclearTimeout函数,通过设置定时器来控制函数的执行时机。

防抖示例代码:

javascript 复制代码
       function debounce(func, delay) {
            // 返回值必须得是函数 keyup 事件处理函数
            return function (args) {
                clearTimeout(func.id); // 清除定时器
                // 对象 , id挂载到func对象上 func是闭包中的自由变量
                func.id = setTimeout(() => func(args), delay);
            };
        }

在示例中,debounce函数接收一个函数func和一个延迟时间delay作为参数,返回一个新的函数。当这个新函数被调用时,它会清除之前可能存在的定时器,并设置一个新的定时器,在delay毫秒后执行原函数func。如果在这delay毫秒内又有新的调用,则会重置定时器,直到最后一次调用后的delay毫秒内没有新的调用,func才会被执行。

详细解析

  1. 函数定义:

    • debounce函数接收两个参数:func是一个需要防抖处理的函数,delay是一个数字,表示延迟执行的时间(通常是毫秒)。
  2. 返回一个包装函数:

    • debounce函数的主体是一个立即执行的返回函数,这个返回的函数接受一个参数args,这实际上是要传递给func的参数。
  3. 清除现有定时器:

    • clearTimeout(func.id); 这行代码的作用是清除之前可能存在的定时器。func.id是一个属性,存储了之前设置的定时器的ID。当func再次被调用时,如果已经有一个定时器在等待执行,那么这个定时器就会被清除,以确保func不会在延迟时间结束后重复执行。
  4. 设置新的定时器:

    • func.id = setTimeout(() => func(args), delay); 这里创建了一个新的定时器,它将在delay毫秒后执行func函数。func函数的参数通过args传递。同时,定时器的ID被存储在func.id中,以便于后续的调用可以清除这个定时器。

节流示例代码:

javascript 复制代码
    const throttle = (func, delay) => {
     
            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); 
                }
            }
        }

在节流的实现中,throttle函数同样接收一个函数func和一个延迟时间delay。返回的新函数会检查上一次执行的时间last,如果当前时间和last之间的差小于delay,则不会立即执行func,而是设置一个定时器,在delay毫秒后执行func并更新last的时间。如果当前时间与last之间的差大于等于delay,则直接执行func并更新last

详细分析

  1. 定义节流函数:

    • throttle是一个箭头函数,接收两个参数:func是要节流的函数,delay是两次调用之间允许的最小时间间隔(毫秒)。
  2. 自由变量初始化:

    • let last, deferTimer; 这里声明了两个变量:last用于存储上一次函数执行的确切时间戳,deferTimer用于存储定时器的ID。
  3. 返回一个包装函数:

    • 返回的匿名函数接受任意数量的参数...args,这些参数将被传递给func
  4. 获取当前时间戳:

    • let now = +new Date(); 这行代码获取当前的时间戳,并将其转换为数字类型。+运算符在这里用于隐式类型转换。
  5. 节流逻辑:

    • if (last && now - last < delay) 这个条件检查last是否存在(即func是否已经被执行过),以及当前时间与上次执行时间的差是否小于delay。如果满足条件,说明func在设定的时间间隔内被多次调用。
  6. 处理在时间间隔内的调用:

    • clearTimeout(deferTimer); 清除之前设置的定时器,以防重复执行。
    • deferTimer = setTimeout(() => { ... }, delay); 设置一个新的定时器,延后delay毫秒后执行func。在这个定时器的回调函数中,更新last为当前时间,并执行func
  7. 处理不在时间间隔内的调用:

    • 如果条件now - last < delay不成立,即func的调用间隔超过了delay,则直接更新last为当前时间,并立即执行func

源码

防抖

js 复制代码
<!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="unDebounced" placeholder="请输入您要搜索的用户名">
        <br>
        <br>
        有防抖的input
        <input type="text" id="debounced" placeholder="请输入您要搜索的用户名">
    </div>
    <script>
        // 不加防抖的
        let inputa = document.getElementById('unDebounced');
        inputa.addEventListener('keyup', (e) => {
            const value = e.target.value;
            fetch("http://localhost:3000/users")
                .then(res => res.json())
                .then(data => {
                    const users = data;
                    // 数组上的新方法,map是转换
                    const filterUsers = users.filter(user => {
                        console.log(user);
                        // 可读性更好 es6 字符串新增方法
                        return user.name.includes(value);
                        // return user.name.indexOf(value) !== -1;
                    })
                    console.log(filterUsers);
                });
        });

        // 加了防抖的
        const inputb = document.getElementById('debounced');
        function handleNameSearch(e) {
            const value = e.target.value;
            fetch('http://localhost:3000/users')
                .then(res => res.json())
                .then(data => {
                    const users = data;
                    // 数组上的新方法,map是转换
                    const filterUsers = users.filter(user => {
                        console.log(user);
                        // return user.name.includes(value);
                        return user.name.includes(value);
                    })
                    console.log(filterUsers);
                });
        }
        // 闭包
        function debounce(func, delay) {
            // 返回值必须得是函数 keyup 事件处理函数
            return function (args) {
                clearTimeout(func.id); // 清除定时器
                // 对象 , id挂载到func对象上 func是闭包中的自由变量
                func.id = setTimeout(() => func(args), delay);
            };
        }
        const debounceNameSearch = debounce(handleNameSearch, 1000);
        inputb.addEventListener('keyup', debounceNameSearch);
    </script>
</body>

</html>
  • json-server:先初始化:npm init -y,再下载安装依赖npm install json-server

db.json

js 复制代码
    {
  "users": [
    {
      "id": "1",
      "name": "wyf"
    },
    {
      "id": "2",
      "name": "lbw"
    },
    {
      "id": "3",
      "name": "xzq"
    }
  ],
  "posts": [
    {
      "id": "7385778376275165225",
      "title": "两数之和你会,三数之和你也会吗?o_O",
      "userId": "1"
    },
    {
      "id": "7385778376275165225",
      "title": "前端新手小白的第一次全栈式开发(AI聊天室)",
      "userId": "2"
    },
    {
      "id": "7385098943941771318",
      "title": "从简单的求和掌握"柯里化",
      "userId": "3"
    },
    {
      "id": "7380274613187084307",
      "title": "前端性能优化实战:掌握图片懒加载技巧",
      "userId": "1"
    }
  ]
}

最后在终端 npm run dev启动json-server

节流

js 复制代码
<!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="unDebounced" placeholder="请输入您要搜索的用户名">
        <br>
        <br>
        有防抖的input
        <input type="text" id="debounced" placeholder="请输入您要搜索的用户名">
    </div>
    <script>
        // 不加防抖的
        let inputa = document.getElementById('unDebounced');
        inputa.addEventListener('keyup', (e) => {
            const value = e.target.value;
            fetch("http://localhost:3000/users")
                .then(res => res.json())
                .then(data => {
                    const users = data;
                    // 数组上的新方法,map是转换
                    const filterUsers = users.filter(user => {
                        console.log(user);
                        // 可读性更好 es6 字符串新增方法
                        return user.name.includes(value);
                        // return user.name.indexOf(value) !== -1;
                    })
                    console.log(filterUsers);
                });
        });

        // 加了防抖的
        const inputb = document.getElementById('debounced');
        function handleNameSearch(e) {
            const value = e.target.value;
            fetch('http://localhost:3000/users')
                .then(res => res.json())
                .then(data => {
                    const users = data;
                    // 数组上的新方法,map是转换
                    const filterUsers = users.filter(user => {
                        console.log(user);
                        // return user.name.includes(value);
                        return user.name.includes(value);
                    })
                    console.log(filterUsers);
                });
        }
        // 闭包
        function debounce(func, delay) {
            // 返回值必须得是函数 keyup 事件处理函数
            return function (args) {
                clearTimeout(func.id); // 清除定时器
                // 对象 , id挂载到func对象上 func是闭包中的自由变量
                func.id = setTimeout(() => func(args), delay);
            };
        }
        const debounceNameSearch = debounce(handleNameSearch, 1000);
        inputb.addEventListener('keyup', debounceNameSearch);
    </script>
</body>

</html>

总的来说,防抖和节流是前端开发中非常有用的技术,能够有效提高应用的性能和用户体验。掌握这两种技术,对于前端开发者来说是非常重要的。

相关推荐
杰哥在此28 分钟前
Python面试题:请解释 Python 中的深拷贝和浅拷贝
开发语言·python·面试·编程
轲轲0135 分钟前
面试题springboot面试
spring boot·后端·面试
阳爱铭1 小时前
GitHub:现代软件开发的协作平台
驱动开发·后端·中间件·面试·架构·github·学习方法
码农超哥同学2 小时前
Python面试题:如何在 Python 中反转一个字符串?
开发语言·windows·python·面试·编程
skyshandianxia2 小时前
java面试八股之MySQL怎么优化查询语句
java·mysql·面试
skyshandianxia4 小时前
Java面试八股之MySQL存储货币数据,用什么类型合适
mysql·面试·职场和发展
xiongxinyu104 小时前
让一个元素水平垂直居中的方式
前端·javascript·css·面试
普通程序员A4 小时前
代码技巧专题 -- 使用策略模式编写HandleService
设计模式·面试·策略模式·代码优化·handle
Neituijunsir5 小时前
2024.06.28 校招 实习 内推 面经
c++·python·算法·面试·自动驾驶·汽车·求职招聘
测试界的世清5 小时前
2024最全软件测试面试八股文(答案+文档+视频讲解)
软件测试·面试·职场和发展