JS总结笔记:防抖动和节流

针对一些会频繁触发的事件如用户的连续点击事件,鼠标滑轮滚动事件,如果正常绑定事件处理函数的话,有可能在很短的时间内多次连续触发事件,十分影响性能

防抖动

防抖动:上次触发事件和这次触发之间满足一定的空闲时间,函数才执行一次

举例:百度搜索。一般搜索绑定输入事件(每次触发输入事件都会触发),如果触发一下就搜索一次,就会给服务器造成巨大压力。所以百度搜索利用防抖动:当用户一直在输入的时候,比如说规定时间0.2秒,0.2秒内触发输入事件触发五次,那么这期间都不会触发远程搜索,当某一次输入后停顿满0.2秒才会去触发远程搜索

原始版:以简单的点击事件为例

//html页面代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <button>btn</button>	//点击按钮
    <script src="./index.js"></script>
</body>
</html>
// 函数防抖动 JS页面代码
var timer = null;	//用变量先定义函数timer的初始状态,这里还没有给timer绑定函数
document.querySelector("button").onclick = function(){	//点击事件
    clearTimeout(timer);
    timer = setTimeout(function(){	//给timer绑定函数
        console.log("如果你在两秒内多次点击,我会等待最后一次点击时间过了2秒才执行!")
    },2000)
}

这里clearTimeout(timer);是关键
第一次点击,因为timer=null,所以clearTimeout(timer);相当于没有执行,接着往下走,给timer绑定了函数,并且执行这个绑定的函数
在2秒内进行了第二次点击,这时timer已经绑定了函数,所以clearTimeout(timer);会清除timer绑定的函数,接着往下走,给timer重新绑定了函数,并且执行这个绑定的函数
所以若是在2秒内多次点击,只会按照最后一次并且等待2秒之后才会执行

升级版:将这个防抖动事件进行封装,方便我们在处理类似的事件时进行调用

var btn=document.querySelector("button");	//获取按钮节点
btn.onclick=debounce(test,2000);	//点击节点调用封装的函数

function debounce(fun, delay) { //fun 需要去抖动的方法,dely 指定的延迟时间
    var timer = null; // 用闭包维护一个timer 设置timer的初始状态
    return function () {
        var context = this;	//用this获取函数的作用域
        var args = arguments; //用argument获取函数的变量	
        clearTimeout(timer); 
        timer = setTimeout(function () {
            fun.apply(context, args);	//这里用apply扩充了fun函数的作用域
        }, delay); // 设定定时器 判断是否已经触发 ,如果触发则重新计时 等待dely毫秒再执行
    }
}
function test(){	//当用户点击时被调用的函数
    console.log("如果你在两秒内多次点击,我会等待最后一次点击时间过了2秒才执行!")
}

这里的args是没有使用的,因为test()函数里并没有传入参数,但是在处理业务的时候很可能会传入参数
至于为什么会使用闭合函数,你需要先理解原始版,相信你会明白的

进化版:我们不希望非要等到事件停止触发后才执行,希望立刻执行函数,然后等到停止触发 2 秒后,才可以重新触发执行函数,这里增加一个immediate参数来设置是否要立即执行:

var btn=document.querySelector("button");
btn.onclick=debounce(test,2000,true);
function debounce(func,delay,immediate){
    var timer = null;
    return function(){
        var context = this;
        var args = arguments;
        if(timer) clearTimeout(timer);
        if(immediate){ //传入的immediate为true的时候执行
            var doNow = !timer;
            //每一次都重新设置timer,就是要保证每一次执行的至少delay秒后才可以执行
            timer = setTimeout(function(){
                timer = null;
            },delay);
            //立即执行
            if(doNow){
                func.apply(context,args);
            }
        }else{//传入的immediate为false的时候执行
            timer = setTimeout(function(){
                func.apply(context,args);
            },delay);
        }
    }
}
function test(){	//当用户点击时被调用的函数
    console.log("如果你在两秒内多次点击,我会等待最后一次点击时间过了2秒才执行!")
}

节流

节流:规定的时间内,多次触发事件,事件绑定的函数只执行一次

举例:很多抢票、抢购这类操作很多都加了节流,为了避免用户大量点击造成服务器压力,当固定时间内,不论点击多少次,只执行一次抢的动作,这种场景,就适合用节流阀技术来实现

主要有两种实现方法:

  • 定时器
  • 时间戳

定时器原始版:以简单的点击事件为例

//函数节流
var timer = true;
document.querySelector("button").onclick = function(){
    if(!timer){		// 判断setTimeout是否执行完成,如果在执行中,则直接return
        return;
    }
    timer = false;
    setTimeout(function(){
        console.log("不管你在2秒内点击多少次,我只执行一次");
        timer = true;
    },2000);
}

当第一次触发事件时,肯定不会立即执行函数,而是在delay秒后才执行
之后连续不断触发事件,也会每delay秒执行一次
当最后一次停止触发后,由于定时器的delay延迟,可能还会执行一次函数

定时器升级版:将定时器进行封装,方便我们在处理类似的事件时进行调用

var throttle = function(func,delay){
    var timer = null;
    return function(){
        var context = this;
        var args = arguments;
        if(!timer){
            timer = setTimeout(function(){
                func.apply(context,args);
                timer = null;
            },delay);
        }
    }
}
function test(){
    console.log("不管你在2秒内点击多少次,我只执行一次")
}
var btn=document.querySelector("button");
btn.onclick=throttle(test,2000);

时间戳原始版(升级版):以简单的点击事件为例

var throttle = function(func,delay){
    var prev = Date.now();
    return function(){
        var context = this;
        var args = arguments;
        var now = Date.now();
        if(now-prev>=delay){
            func.apply(context,args);
            prev = Date.now();
        }
    }
}
function test(){
    console.log("不管你在2秒内点击多少次,我只执行一次")
}
var btn=document.querySelector("button");
btn.onclick=throttle(test,2000);

当高频事件触发时,第一次应该会立即执行(给事件绑定函数与真正触发事件的间隔如果大于delay的话),而后再怎么频繁触发事件,也都是会每delay秒才执行一次,而当最后一次事件触发完毕后,事件也不会再被执行了

综合使用时间戳与定时器,完成一个事件触发时立即执行,触发完毕还能执行一次的节流函数:

var throttle = function(func,delay){
    var timer = null;
    var startTime = Date.now();
    
    return function(){
        var curTime = Date.now();
        var remaining = delay-(curTime-startTime);
        var context = this;
        var args = arguments;

        clearTimeout(timer);
        if(remaining<=0){
            func.apply(context,args);
            startTime = Date.now();
        }else{
            timer = setTimeout(func,remaining);
        }
    }
}
function test(){
    console.log("我是综合版本")
}
var btn=document.querySelector("button");
btn.onclick=throttle(test,2000);

需要在每个delay时间中一定会执行一次函数,因此在节流函数内部使用开始时间、当前时间与delay来计算remaining,当remaining<=0时表示该执行函数了,如果还没到时间的话就设定在remaining时间后再触发。当然在remaining这段时间中如果又一次发生事件,那么会取消当前的计时器,并重新计算一个remaining来判断当前状态。

总结

防止一个事件频繁触发回调函数的方式:

防抖动:将几次操作合并为一此操作进行
原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发

节流:使得一定时间内只触发一次函数。
它和防抖动最大的区别就是,节流函数不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而防抖动只是在最后一次事件后才触发一次函数
原理是通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器

这里附上我参考的两篇文章的地址
入门:https://blog.csdn.net/github_39132847/article/details/82984971
进阶:https://blog.csdn.net/crystal6918/article/details/62236730

你可能感兴趣的:(JS总结笔记)