zoukankan      html  css  js  c++  java
  • 【函数】防抖和节流

    目录:

    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>

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

  • 相关阅读:
    Linxu 挂载光盘和硬盘
    Linux firewall
    指纹获取 Fingerprint2
    Vue 封装的组件生命周期钩子
    vue富文本编辑,编辑自动预览,单个图片上传不能预览的问题解决:
    vue 集成百度富文本编辑器
    axios 的二次封装
    element 列表中已选的标记
    element 表单的input循环生成,并可单个input失去焦点单个验证并保存; (多个表单实例)
    axios 二进制流导出
  • 原文地址:https://www.cnblogs.com/xiaoxuStudy/p/12653666.html
Copyright © 2011-2022 走看看