JS防抖和节流


函数防抖和节流:解决频繁性的事件触发,实现前端页面性能优化。

情景
<body>
    <div class="box"></div>
    <script>
        let box=document.querySelector(".box");
        let count=0;
        function doSomething(){
            box.innerHTML=count++;
        }
        box.onmousemove=doSomething;
    </script>
</body>

在这里插入图片描述

当鼠标在box上移动时,就会频繁触发doSomething(),其中可能发ajax请求,如果不加以限制的话,这将会严重影响页面性能。此时,防抖与节流就可以派上用场啦~

借助underscore实现

underscore中的防抖函数:_.debounce(事件处理函数,时间间隔,是否立即执行(默认false))

  • 事件处理函数doSomething是在1000ms之后才执行,如果在这段时间内再次调用,则会重新计算执行时间

  • 当预定的时间内没有再次调用该函数,则执行doSomething()

<div class="box"></div>
<script src="./underscore-min.js"></script>
<script>
   let box=document.querySelector(".box");
   let count=0;

   function doSomething(){
       box.innerHTML=count++;
   }
   box.onmousemove=_.debounce(doSomething,1000);
</script>

节流函数:_.throttle(事件处理函数,时间间隔,{leading:boolean(第一次是否触发),trailing:boolean(最后一次是否触发)}),但是leadding和trailing中至少要有一个为true

  • 持续触发mousemove事件,每隔1000ms,触发一次回调
box.onmousemove=_.throttle(doSomething,1000,{
    leading:true,
    trailing:false
});

防抖和节流是很常用的解决频繁事件回调(造成页面性能消耗以及卡顿现象)的方法,但是我们只使用_.debounce()和_.throttle()两个函数没必要引入整个underscore,因此我们在平常的开发中,可以自己先封装好这两个函数。

防抖(debounce)
  • 防抖和节流函数都是返回一个函数
应用场景
  • scroll事件滚动触发(滚动完毕再执行)
  • 搜索框输入查询(输入完毕才发送请求)
  • 表单验证(输入完后才验证)
  • 按钮提交(连续点击按钮)
  • 浏览器窗口缩放(resize事件)
基本实现
  • 注意处理doSomething()中的this指向和event对象
function doSomething(e){
    console.log(this);//this指向Window
    console.log(e);//undefined

    box.innerHTML=count++;
}

function debounce(func,time){
    let timeout;
    return function(){
        let that=this;//this指向当前dom元素
        let args=arguments;//事件对象
        
        if(timeout){
            clearTimeout(timeout);
        }
        timeout=setTimeout(()=>{
            //改变doSomething()中的this指向,传入event对象
            func.apply(that,args);
        },time);
    }
}

box.onmousemove=debounce(doSomething,1000);
立即执行

debounce函数的第三个参数immediate,控制是否立即执行,默认为false,当前还需要处理当immediate为true的情况

当immediate为true的时候,由于immediate的值一直都是true,因此我们定义一个变量flag(!timeout)来标记是否立即调用回调函数,在时间间隔内,将timeout置为null,flag为true的时候立即调用func函数

function debounce(func,time,immediate){
    let timeout;
    return function(){
        let that=this;
        let args=arguments;
        
        if(timeout){
            clearTimeout(timeout);
        }
        if(immediate){
            let flag=!timeout;

            timeout=setTimeout(()=>{
                timeout=null;
            },time);

            if(flag){
                func.apply(that,args);
            }
        }else{
            timeout=setTimeout(()=>{
                func.apply(that,args);
            },time);
        }
    }
}
box.onmousemove=debounce(doSomething,1000,true);
返回值和取消操作

有时候需要用到回调函数的返回值,可以在debounce函数中定义一个变量保存func()的返回值,然后在最后返回

有时候需要在等待时间内取消事件回调,可以将要返回的函数赋值给一个变量,给函数对象添加一个cancel方法,清除定时器

——完整版

<!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>
        .box{
            width: 100%;
            height: 200px;
            line-height: 200px;
            text-align: center;
            font-size: 50px;
            background-color: blueviolet;
            color: #fff;
        }
    </style>
</head>
<body>
    <div class="box"></div>
    <button id="btn">取消</button>
    <script src="./underscore-min.js"></script>
    <script>
        let box=document.querySelector(".box");
        let btnObj=document.querySelector("#btn");
        let count=0;

        function doSomething(e){
            box.innerHTML=count++;
        }

        function debounce(func,time,immediate){
            let timeout;
            let res;
            let debounced=function(){
                let that=this;
                let args=arguments;
              
		        if(timeout){
		            clearTimeout(timeout);
		        }
                if(immediate){
                    let flag=!timeout;
                    timeout=setTimeout(()=>{
                        timeout=null;
                    },time);
                    if(flag){
                        res=func.apply(that,args);
                    }
                }else{
                    timeout=setTimeout(()=>{
                        res=func.apply(that,args);
                    },time);
                }
                return res;
            }

            debounced.cancel=function(){
                clearTimeout(timeout);
                timeout=null;
            }

            return debounced;
        }

        let doSome=debounce(doSomething,1000);
        btnObj.onclick=doSome.cancel;
        box.onmousemove=doSome;
    </script>
</body>
</html>
节流(throttle)
  • 如果持续触发事件,每隔一段时间,触发一次
应用场景
  • DOM元素的拖拽功能实现
  • 射击游戏类
  • 计算鼠标移动的距离
  • 监听scroll滚动事件,和防抖有所不同
使用时间戳完成节流函数({leading:true,trailing:false})

第一次会触发,最后一次不会触发

思路:设置一个旧的时间戳old和一个新的时间戳now,若old和now之间的时间差大于时间间隔time则触发一次func回调。初始的时候,将old设置成0,这样鼠标刚移入的时候,时间差必然大于time,因此第一次会触发。

注意更新old的值

function throttle(func,time){
   let old=0;
   return function(){
       let that=this;
       let args=arguments;
       let now=Date.now();

       if(now-old>time){
           func.apply(that,args);
           old=now;
       }
   }
}
使用定时器完成节流函数({leading:false,trailing:true})

第一次不会触发,最后一次会触发

思路:利用定时器timeout判断并重新设置定时器执行func函数,鼠标移入的时候timeout没有赋值,取反为true,经过时间间隔time之后调用func函数,同时将timeout设置为null,这样,在时间间隔之后timeout为null,取反又满足条件,继续执行。因此第一次不会触发,最后一次会触发

function throttle2(func,time){
    let timeout;
    return function(){
        let that=this;
        let args=arguments;
        
        if(!timeout){
            timeout=setTimeout(()=>{
                timeout=null;
                func.apply(that,args);
            },time);
        }
    }
}
合并、处理第三个参数
function throttle(func,time,options){
    let old=0;
    let timeout;

    return function(){
        let that=this;
        let args=arguments;
        let now=Date.now();

        if(!old&&options.leading===false){
            old=now;
        }
        if(now-old>time){
            if(timeout){
                clearTimeout(timeout);
                timeout=null;
            }
            func.apply(that,args);
            old=now;
        }else if(!timeout&&options.trailing==true){
            timeout=setTimeout(()=>{
                old=Date.now();
                timeout=null;
                func.apply(that,args);
            },time);
        }
    }
}

box.onmousemove=throttle(doSomething,1000,{leading:true,trailing:true});

throttle函数同样也可以像debounce函数一样处理返回值或者设置取消操作。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值