Javascript-节流的实现

607 阅读7分钟

1.节流的原理:

🌵节流:如果你持续触发事件,每隔一段时间,只执行一次。

例子🌰是通过underscore的throttle实现的小例子:

//index.html
<!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>
        #container{
            width100%;
            height200px;
            line-height200px;
            text-align: center;
            background: pink;
            color:rebeccapurple;
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <!-- <button id="btn">取消防抖</button> -->
    <script src="https://cdn.bootcdn.net/ajax/libs/underscore.js/1.10.2/underscore-min.js"></script>

    <script src="./throttle.js"></script>
</body>
</html>

throttle.js文件:

let count=0;
let container=document.querySelector("#container");

function dosomething(){
    container.innerHTML=count++;
};

let doit= _.throttle(dosomething,2000,
    {
    leading:false,
    trailing:true
});

container.onmousemove=doit;

2.使用时间戳来实现第一版节流函数:

"不顾头,顾尾":实现了首次触发事件的时候,立即执行,最后一次的离开的时候,不执行。

🍊使用underscore实现:

//进入的时候立即执行,离开的时候不执行
let doit= _.throttle(dosomething,2000,
    {
    leading:true,
    trailing:false
});

🍊使用时间戳来实现:

function throttle(func,wait){
    let context,args;
    let old=0;
    return function(){
        //获取现在的时间戳
        let now=new Date().valueOf();
        context=this;
        if(now-old>wait){
            func.apply(context,args);
            //操作执行完毕后,让old等于now
            old=now;
        };
    }
}

let count=0;
let container=document.querySelector("#container");

function dosomething(){
    container.innerHTML=count++;
};

//自己通过时间戳实现的节流
let dome=throttle(dosomething,2000);

container.onmousemove=dome;

注意的点

  • 时间戳的获得:new Date().valueOf();
  • 再有就是underscore的throttle的第三个参数是一个对象:
    • 参数对象:{leading:false,trailing:true};
    • leading和trailing这两个参数同时为false的时候会出bug~!!!

3.使用定时器来完成节流函数:

"顾头,不顾尾"--- 进入的时候不立即执行,离开时还会执行一次

🍊使用underscore实现:

//进入的时候不立即执行,离开时还会执行一次
let doit1= _.throttle(dosomething,2000,
    {
    leading:false,
    trailing:true
});

🍊使用定时器实现:

//定时器版本的节流函数

function throttleit(func,wait){
    let context,args,timer;
    return function(){
        context=this;
        args=arguments;
        if(!timer){      //对timer进行取反
            timer=setTimeout(function(){
                func.apply(context,args);
                timer=null;      //事件执行完了之后需要对timer计时器进行清理
            },wait)
        }
    }
};

4.时间戳和定时器双剑合并,实现节流函数:

🍊使用underscroe实现:

//进入的时候会立即执行,离开后还会再执行一次
let doit2= _.throttle(dosomething,2000,
    {
    leading:true,
    trailing:true
});

🍊使用定时器和时间戳取长补短实现:

//定时器时间戳混合双打篇
function throttleDouble(func,wait){
    let context,args,timer;
    let old=0;
    return function(){
        context=this;
        //获取当前函数的实参
        args=arguments;
        //获得时间戳
        let now=new Date().valueOf();

        //在首次进入的时候,能够保证立即执行。
        if(now-old>wait){
            if(timer){
                clearTimeout(timer);
                timer=null;
            }
            func.apply(context,args);
            old=now;
        };
        //在后续的执行过程中完成每wait时间内,执行一次。
        if(!timer){
            timer=setTimeout(function(){
                old=new Date().valueOf();
                timer=null;
                func.apply(context,args);
            },wait)
        }
    };
}

🌵思路分析:

  • 时间戳能够实现在首次进入的时候立即执行。
  • 定时器能够实现每个wait时间内,执行一次。

5.函数节流完整优化:

🌵上面的2、3、4分成了三种情况:

  • 第一次会执行,离开时不执行:

    let doit= _.throttle(dosomething,2000,
        {
        leading:true,
        trailing:false
    });
    
  • 第一次不执行,离开时执行:

    //进入的时候不立即执行,离开时还会执行一次
    let doit1= _.throttle(dosomething,2000,
        {
        leading:false,
        trailing:true
    });
    
  • 第一次执行,离开时也执行:

    let doit2= _.throttle(dosomething,2000,
        {
        leading:true,
        trailing:true
    });
    

🌵代码实现:

function throttleAll(func,wait,options){
    let context,args,timer;
    let old=0;
   //如果没有设置第三个参数,那么第三个参数为空对象
    if(!options) options={};
  
    let later=function(){
        old=new Date().valueOf();
        timer=null;
        func.apply(context,args);
    }

    return function(){
        context=this;
        //获取当前函数的实参
        args=arguments;
        //获得时间戳
        let now=new Date().valueOf();

        //
        if(options.leading===false && !old){
            old=now;
        }
        //在首次进入的时候,能够保证立即执行。
        if(now-old>wait){
            if(timer){
                clearTimeout(timer);
                timer=null;
            }
            func.apply(context,args);
            old=now;
        };
        //在后续的执行过程中完成每wait时间内,执行一次。
        if(!timer &&options.trailing!==false){
            timer=setTimeout(later,wait)
        }
    };
}