防抖和节流

防抖和节流

防抖处理

​ 有时候高频的触发函数是我们不想见到的,于是我们可以使用防抖处理去解决这个问题。防抖,顾名思义,不管你怎么抖,我都只会触发高频连续函数的最后一次。

防抖简易版

​ 我们通常会使用window的setTimeout方法处理防抖函数,代码如下:

function debounce(fn, await){
    let timer = null;
    return function(){
        if(timer){
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            fn(fn.arguments);
        }, await);
   }
}

​ 这里我们使用闭包去存储setTimeout方法的变量,当下一次触发防抖函数的时候,会取消上一次的setTimeout方法。讲的简单点就是如果我们触发了一次该函数,在await时间内又触发了,上一次的setTimeout中的fn函数还没有执行呢,就被清除了,取而代之的是新的setTimeout方法,在await时间后去执行fn函数。

实验代码如下,在1s内高频点击只会触发最后一次。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">点我一下</button>
</body>
<script>
    function debounce(fn, await){
        let timer = null;
        return function(){
            if(timer){
                clearTimeout(timer);
            }
            timer = setTimeout(() => {
                fn(fn.arguments);
            }, await);
        }
    }
    function test(){
        console.log("啊!!!,我被点击了");
    }
    document.querySelector('#btn').addEventListener('click', debounce(test,1000));
</script>
</html>

this的指向问题

​ 但是上面的防抖处理是有问题的,我举个例子大家就明白了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">点我一下</button>
</body>
<script>
    function debounce(fn, await){
        let timer = null;
        return function(){
            if(timer){
                clearTimeout(timer);
            }
            timer = setTimeout(() => {
                fn(fn.arguments);
            }, await);
        }
    }
    function test(event){
        console.log("我的this指向的是" + this);
        console.log("我的event是" + event);
    }
    document.querySelector('#btn').addEventListener('click', debounce(test,1000));
</script>
</html>

当我们点击按钮的时候,结果并不是我们所希望的结果。
在这里插入图片描述

test函数里的this竟然是指向window,event没有了。我们这可是希望绑定给button的啊,无语了。

我们来看下一个例子就明白了,大家来猜猜结果是指向小黄还是小白。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
<script>
    const obj1 = {
    name:"小黄",
    sayHello(fn){
        fn();
    }
}
const obj2 = {
    name:"小白",
    fn(){
        console.log(this);
    }
}
obj1.sayHello(obj2.fn);
</script>
</html>

结果发现都不是,哈哈,指向的还是是window。
在这里插入图片描述

所以我们大概能想到,这种将函数作为参数传递给另一个函数,并在另一个函数中使用会使this丢失。我们需要在防抖处理函数中纠正它的this指向。代码如下:

function debounce(fn, await){
       	let timer = null;
        return function(){
            if(timer){
                clearTimeout(timer);
            }
            timer = setTimeout(() => {
                fn.apply(this, arguments);//就只有这里发生了改变。
            }, await);
        }
    }
//apply方法改变函数的this指向,具体的函数规则可以自行百度,我们只需要知道这个这个方法将fn的this强行改成了debounce作用域的this。

结果就正常了:
在这里插入图片描述

加上立即执行功能

​ 我们可以在上面的代码进行改变,以上的处理是在await时间后再执行fn函数,我们希望可以触发debounce函数时立即执行fn函数,在高频触发debounce函数只执行第一次,代码如下:

function debounce(fn, await, immediate){//fn定义的函数,await等待的时间,immediate是是立即执行
    let timer = null;
    return function(){
        if(timer){
            clearTimeout(timer);
        }
        if(immediate){
            if(timer === null){
                fn.apply(this, arguments);
            }
            timer = setTimeout(()=>{
                timer = null;
            },await);
        }else{
            timer = setTimeout(() => {
                fn.apply(this, arguements);
            }, await);
            console.log();
        }
    }
}

​ 当immediate取true的时候证明开启立即执行,这里的timer起到了存储上一次等待时间是否过完了,如果没有过完,timer就不是null,也就不会执行fn函数。只有上一次的等待时间过完了,才会执行fn函数。思路和上面的差不多,我就不给大家展示测试的结果了。

节流处理

​ 和防抖处理一样,都是解决高频触发函数的处理方式,不过它的思路不是取多次高频触发的最后一次,而是只有在确定时间间隔过了才会触发。举个例子:我们在5秒内触发了20次函数,间隔为2s,那么经过节流处理函数只会触发两次,在2s和4s的时候。不说了,直接show code:

function jieliu(fn, await){
        let timer = null;
        let flag = true;
        return function(){
            if(flag){
                timer = setTimeout(() => {
                    fn.apply(this, arguments);
                    flag = true;
                }, await);
            }
            flag = false;
        }
    }

​ 这里的flag用来存储上一次上一次setTimeout是否执行完,注意setTimeout是异步执行的,所以理论上flag一直都是false,只有上一次的setTimeout执行完,下一次的setTimeout才能被触发。

实验代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">点我一下</button>
</body>
<script>
    function jieliu(fn, await){
        let timer = null;
        let flag = true;
        return function(){
            if(flag){
                timer = setTimeout(() => {
                    fn.apply(this, arguments);
                    flag = true;
                }, await);
            }
            flag = false;
        }
    }
    function test(){
        console.log("我被触发了");
    }
        
    document.querySelector('#btn').addEventListener('click', jieliu(test,1000));
</script>
</html>

节流增强版

​ 当然也会发生this丢失的问题,我们在上面的基础上进行改进,和防抖处理改进的思路一样。

function jieliu(fn, await, immediate){
    let timer = null;
    let mark = true;
    return function(){
        if(mark){
            if(immediate){
                fn.call(this, ...arguments);
                timer = setTimeout(()=>{
                    mark = true;
                },await)
            }else{
                timer = setTimeout(()=>{
                    fn.call(this, ...arguments);
                    mark = true;
                },await)
            }
        }
        mark = false;
    }
}

实验代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="btn">点我一下</button>
</body>
<script>
    function jieliu(fn, await, immediate){
    let timer = null;
    let mark = true;
    return function(){
        if(mark){
            if(immediate){
                fn.call(this, ...arguments);
                timer = setTimeout(()=>{
                    mark = true;
                },await)
            }else{
                timer = setTimeout(()=>{
                    fn.call(this, ...arguments);
                    mark = true;
                },await)
            }
        }
        mark = false;
    }
}
    function test(event){
        console.log("我被触发了");
        console.log("我的this指向的是" + this);
        console.log("我的event是" + event);
    }
        
    document.querySelector('#btn').addEventListener('click', jieliu(test,1000, true));
</script>
</html>

注意事项

​ 我们在解决this指向的问题时,在setTimeout的回调函数中用的是箭头函数,箭头函数的this指向的是外部环境的this。如果你用普通函数,指向的就是window了,因为setTimeout是window的方法,需要用变量将外部环境的this保存下来。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值