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

相声

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

【防抖侠登场】 防抖侠,江湖人称"耐心大师",他最擅长的就是让人冷静下来。比如说吧,村里有个搜索框,用户一激动,手指头就跟开了挂似的,键盘啪啪啪地响。这要是没个节制,服务器非得被折腾得七荤八素不可。防抖侠说:"别急,让我来。"他施展绝技"耐心等待",告诉用户:"你先歇会儿,等你手停了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>

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

相关推荐
拉不动的猪2 小时前
# 关于初学者对于JS异步编程十大误区
前端·javascript·面试
熊猫钓鱼>_>4 小时前
Java面向对象核心面试技术考点深度解析
java·开发语言·面试·面向对象··class·oop
进击的野人7 小时前
CSS选择器与层叠机制
css·面试
T___T9 小时前
全方位解释 JavaScript 执行机制(从底层到实战)
前端·面试
9号达人9 小时前
普通公司对账系统的现实困境与解决方案
java·后端·面试
勤劳打代码10 小时前
条分缕析 —— 通过 Demo 深入浅出 Provider 原理
flutter·面试·dart
努力学算法的蒟蒻10 小时前
day10(11.7)——leetcode面试经典150
面试
进击的野人11 小时前
JavaScript 中的数组映射方法与面向对象特性深度解析
javascript·面试
南山安11 小时前
以腾讯面试题深度剖析JavaScript:从数组map方法到面向对象本质
javascript·面试
橘颂TA12 小时前
【剑斩OFFER】算法的暴力美学——二分查找
算法·leetcode·面试·职场和发展·c/c++