• 博客园logo
  • 会员
  • 周边
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
小许学习笔记
博客园    首页    新随笔    联系   管理    订阅  订阅
【函数】防抖和节流

目录:

1、什么是防抖和节流

2、防抖和节流的实现

3、防抖和节流在开发中的使用场景

 

什么是防抖和节流

  防抖和节流是函数的防抖和函数的节流。在进行窗口的 resize、scroll、输入框内容校验等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。此时,可以采用 debounce (防抖) 和 throttle(节流)的方式来减少调用频率,同时又不影响实际效果。

  函数防抖(debounce):  当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才执行一次,如果设定的事件到来之前,又一次触发了事件,就重新开始延时。

  前端开发过程中的 resize, scroll, mousemove, mousehover 等,会被频繁地触发,不做限制的话,有可能一秒之内执行几十次、几百次,如果在这些函数内部执行了其他函数,尤其是执行了操作 DOM 的函数,那不仅会造成计算机资源的浪费,还会降低程序运行速度,甚至造成浏览器卡死、崩溃。除此之外,重复的 AJAX 调用不仅会造成数据关系的混乱,还会造成网络阻塞,增加服务器压力,显然这个问题也是需要解决的。

  “函数防抖” 的关键在于,在一个动作发生的一定时间之后,才执行特定事件。

 

防抖和节流的实现 

防抖

  下面是事件触发函数频繁执行的情况:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        #content{
            width: 200px;
            height: 200px;
            line-height: 200px;
            background-color: #ccc;
            margin:0 auto;
            font-size: 60px;
            text-align: center;
            color: #000;
            cursor: pointer;           
        }
    </style>
</head>
<body>
    <div id="content"></div>
    <script>
        let num = 1;
        let oDiv = document.getElementById('content');
        let changeNum = function(){
            oDiv.innerHTML = num++;
        }
        oDiv.onmousemove = changeNum;
    </script>
</body>
</html>

页面效果:

当鼠标在方块内划过时事件被触发,方块内的数字在疯狂地变动。

 

 

  使用防抖函数去改进它。

  防抖函数是在一个动作发生的一定时间之后,才执行特定事件。根据这个特性,自然而然地想起 setTimeout。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        #content{
            width: 200px;
            height: 200px;
            line-height: 200px;
            background-color: #ccc;
            margin:0 auto;
            font-size: 60px;
            text-align: center;
            color: #000;
            cursor: pointer;           
        }
    </style>
</head>
<body>
    <div id="content"></div>
    <script>
        let num = 1;
        let oDiv = document.getElementById('content');
        let changeNum = function(){
            oDiv.innerHTML = num++;
        }
        let deBounce = (fn, delay) => {             
            let timer = null;                       
            return function(){                      
                if( timer ){                        
                    clearTimeout(timer);            
                }                                   
                timer = setTimeout(function(){      
                    fn();                           
                }, delay)                           
            }                                       
        }                                           
        oDiv.onmousemove = deBounce(changeNum, 500);
</script>
</body>
</html>

  上面,首先定义一个函数 deBounce ,接收 2 个参数,参数 fn 是需要延迟执行的函数,参数 delay 指定一个时间。deBounce 函数返回一个函数,这个函数是 setTimeout 。为防止 setTimeout 累加定义了一个变量 timer,变量的初始值是 null, 它接受 setTimeout 的返回值,在执行 setTimeout 前会用 clearTimeout() 方法取消由 setTimeout() 方法设置的 timeout。当 deBounce 函数定义完成以后,给 oDiv 绑定一个事件,deBounce 接收第一个参数是 changeNum, 第二个参数指定延迟时间为 500 毫秒。

   在浏览器查看页面效果时会发现,当在方块频繁地触发事件时,事件不会被频繁地触发,且鼠标移开方块事件只会触发一次,这说明防抖函数基本有效。

   上面的防抖函数并不完善,setTimeout 的 this 指向 window,但是却是通过 oDiv 去调用的,所以需要在 fn 执行的时候绑定 this。

  可以输出一下 this 确定一下 this 是不是指向window的。在浏览器打开触发事件在控制台可以看到 this 是指向 window 的。

        let deBounce = (fn, delay) => {
            let timer = null;
            return function(){
                if( timer ){
                    clearTimeout(timer);
                }
                timer = setTimeout(function(){
                    fn();
                    console.log( this );
                }, delay)
            }
        }

 

  可以使用 apply 方法绑定 this 作用域。但是有更简单的方法,利用箭头函数的特性去绑定 this 的作用域。

  直接将 setTimeout 第一个参数用箭头函数来表示,在浏览器验证一下,可以看到此时 this 指向 div。

timer = setTimeout(()=>{
      fn();
      console.log( this );
}, delay)

  (箭头函数可参考:https://es6.ruanyifeng.com/#docs/function#箭头函数 )

   因为箭头函数没有自己的 this 作用域,this 是来自作用域链,同时也没有自己的 arguments。如果是需要绑定 arguments ,可以通过扩展运算符来绑定。

        let deBounce = (fn, delay) => {
            let timer = null;
            return function(...args){
                if( timer ){
                    clearTimeout(timer);
                }
                timer = setTimeout(()=>{
                    fn(...args);
                }, delay)
            }
        }

  至此,一个简单的函数防抖功能就完成了。

 

节流

  函数节流( throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。

  防抖和节流的作用都是防止函数多次调用。

下面来看一下函数防抖和函数节流的区别:

  下面有 2 个button,点击 “ 防抖按钮 ” 调用 deBounce 函数,点击 “ 节流按钮 ”调用 throttle 函数。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .div1{
            width: 500px;
            height: 300px;
            line-height: 300px;
            margin: auto;
            text-align: center;
        }
        button{
            width: 100px;
            height:30px;
        }
    </style>
</head>
<body>
    <div class="div1">
        <button>防抖按钮</button>
        <button>节流按钮</button>
    </div>
    <script>
        let fn = ()=>{
            console.log('我被触发了');
        }
        let deBounce = (fn, delay) => {
            let timer = null;
            return function( ...args ){
                if( timer ){
                    clearTimeout( timer );
                }
                timer = setTimeout(()=>{
                    fn( ...args );
                }, delay)
            }
        }
        let throttle = (fn, delay) => {
            let flag = true;
            return function (...args) {
                if (!flag) return;
                flag = false;
                setTimeout(() => {
                    fn(...args);
                    flag = true;
                }, delay)
            }
        }
        let deBounceButton = document.getElementsByTagName('button')[0];
        let throttleButton = document.getElementsByTagName('button')[1];
        deBounceButton.onclick = deBounce(fn, 1000);
        throttleButton.onclick = throttle(fn, 1000);
    </script>
</body>
</html>

 

 

   在十秒内连续不断点击 “ 防抖按钮 ”,停止点击后发现防抖函数只被触发了一次。在十秒内连续不断点击 “ 节流按钮 ”,停止点击后发现节流函数被触发了 10 次。

  可以看出,防抖关注的是一定时间连续触发只在最后一次执行,节流侧重于一段时间内执行一次。

 

下面来看看怎么实现函数节流:

  运行以下代码,打开浏览器查看效果,发现并没有实现节流。

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6     <title>Document</title>
 7 </head>
 8 <body>
 9     <button>点击</button>
10     <script>
11         let oButton = document.getElementsByTagName('button')[0];
12         let fn = ()=>{
13             console.log( '我被响应了' );
14         }
15         let throttle = (fn, delay)=>{     
16             return function(){            
17                 setTimeout(()=>{          
18                     fn();                 
19                 }, delay);                
20             }                             
21         }                                 
22         oButton.onclick = throttle(fn, 500);
23     </script>
24 </body>
25 </html>

   可以利用开关变量来实现节流功能。

        let throttle = (fn, delay) => {
            let flag = true;
return function (...args) {
                if (!flag) return;
                flag = false;
                setTimeout(() => {
                    fn(...args);
                    flag = true;
                }, delay)
            }
        }

  至此,实现了节流。也可以通过时间戳来实现防抖和节流。

 

防抖和节流在开发中的使用场景

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6     <title>Document</title>
 7 </head>
 8 <body>
 9     <input type="text">
10     <script>
11         let deBounce = (fn, delay) => {
12             let timer = null;
13             return function( ...args ){
14                 if( timer ){
15                     clearTimeout( timer );
16                 }
17                 timer = setTimeout(()=>{
18                     fn( ...args );
19                 }, delay)
20             }
21         }
22         let oInput = document.getElementsByTagName( 'input' )[0];
23         let ajax = (content) => {
24             let message = content;
25             let json = { message };
26             console.log( JSON.stringify( json ) );
27         }
28         let doAjax = deBounce( ajax, 2000 );
29         //不使用防抖
30         oInput.addEventListener('keyup', (e)=>{
31             console.log( e.target.value );
32         })
33     </script>
34 </body>
35 </html>

  上面的代码执行的结果是:只要按下键盘输入数据,就会触发模拟的 AJAX 请求,这样不仅十分浪费资源,而且在实际的运用中,用户也是输出完整的字符后才会发起请求。

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <meta name="viewport" content="width=device-width, initial-scale=1.0">
 6     <title>Document</title>
 7 </head>
 8 <body>
 9     <input type="text">
10     <script>
11         let deBounce = (fn, delay) => {
12             let timer = null;
13             return function( ...args ){
14                 if( timer ){
15                     clearTimeout( timer );
16                 }
17                 timer = setTimeout(()=>{
18                     fn( ...args );
19                 }, delay)
20             }
21         }
22         let oInput = document.getElementsByTagName( 'input' )[0];
23         let ajax = (content) => {
24             let message = content;
25             let json = { message };
26             console.log( JSON.stringify( json ) );
27         }
28         let doAjax = deBounce( ajax, 2000 );
29         //使用防抖
30         oInput.addEventListener('keyup', (e)=>{
31             doAjax( e.target.value );
32         })
33     </script>
34 </body>
35 </html>

  上面的代码执行的结果是:当在频繁地输入时不会发起请求,只有当在指定的间隔内没有输入时,才会去执行函数,如果停止输入,但是在指定的间隔内又输入,会重新触发计时。当用户在不断输入值时,用防抖来请求节约资源。

 

posted on 2020-06-01 16:55  xiaoxustudy  阅读(553)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3