节流函数throttle

443 阅读2分钟

什么是节流函数?

当事件触发时,会触发事件的响应函数,如果这个事件频繁的触发,那么这个节流函数会按照一定的频率执行函数,不管中间有多少次触发这个事件,执行函数的频率是固定的。简单来说:我在10s内触发了十次事件,但是我设置的节流函数是10s的周期触发一次,那么只会执行一次事件的业务代码。

image.png 所以我们需要一个时间频率interval为2s,表示在几秒内触发一次事件, nowTime是下一次触发事件时间,lastTime是上一次触发事件的时间,所以当两者的差大于等于interval的时候,也就是触发下一次响应函数的时候, interval - (nowTime - lastTime)

基本实现

throttle.js

function throttle(fn, interval) {
    let lastTime = 0
    const _throttle = function() {
        const nowTime = new Date().getTime()
        const remainTime = interval - (nowTime - lastTime)
        //当触发响应函数时,将nowTime赋值给lastTime,也就是保存当前函数触发的时间,用于和下一次做时间间隔作比较。
        if (remainTime <= 0) {
            fn()
            lastTime = nowTime
        }
    }
    return _throttle
}

throttle.html

<input type="text">
    <button>取消</button>
    <script src="./throttle.js">
    </script>
    <script>
        let inp = document.querySelector('input')
        let btn = document.querySelector('button')
        let count = 0

        function fn() {
            count++
            console.log(`发送了` + count + '次请求');
        }


        inp.oninput = throttle(fn, 2000)
    </script>

以上的代码会在刚开始的运行的时候,会自动触发一次响应函数,那么为什么会触发呢? 原因是因为nowTime刚开始是一个非常大的时间数字,lastTime刚开始默认是0,所以这里的remainTime是小于零的,所以第一次响应函数是默认执行的。

解决第一次事件不要立即触发

所以我们还需要一个变量来控制第一次是否执行,申明一个对象,里面保存变量leading默认为false,我们上面已经分析过第一次就执行的问题,所以这里我们只需要将lastTime在leading

function throttle(fn, interval,options = {leading:false}) {
    let lastTime = 0
    //将其解构出来
    const {leading} = options
    const _throttle = function() {
        const nowTime = new Date().getTime()
        const remainTime = interval - (nowTime - lastTime)
        //当触发响应函数时,将nowTime赋值给lastTime,也就是保存当前函数触发的时间,用于和下一次做时间间隔作比较。
        if (remainTime <= 0) {
            fn()
            lastTime = nowTime
        }
    }
    return _throttle
}

解决最后一次执行

也就是我们不停的发送请求,当最后一次发送请求在节流周期之内,比如我们在第10s-第12s开始,我们发送请求发送到一半,突然不发送了假设我们到达了11s,本来这个时候应该是不执行请求的,因为此时并没有到达12s发送请求的临界值,这个时候我们就让他把这个请求自动发送出去。 可能比较难理解。 于是我们将最后剩余的时间加入定时器,利用定时器来执行最后一次任务,所以加入以下代码

if(trailing){
   setTimeout(()=>{
       fn()
   },remainTime)
}

但是这样一来加入的定时器就太多了,当我们在输入框输入内容的时候,也就是模拟发送的请求,我们每输入一个内容,就会产生一个remainTime,那么这么一来,就会产生很多定时器,然后就会出现这样的情况;我输入了七次内容,发送了八次请求;所以定时器这里还得进行限制。让他在我们停止输入内容的时候进行定时器发送。

image.png

改进1

let t= null
if(remainTime <= 0){
  if(t){
   clearTimeout(t)
   t = null
  }  
}
if(trailing){
  t =  setTimeout(()=>{
       t = null
       fn()
   },remainTime)
}
function throttle(fn, interval, options = { leading: false, trailing: false }) {
    // 表示第一次是触发的,最后一次不触发
    let lastTime = 0
    const { leading, trailing } = options
    let timer = null
    const _throttle = function() {
        let nowTime = new Date().getTime()
        if (!leading && !lastTime) {
            lastTime = nowTime
        }
        let remainTime = interval - (nowTime - lastTime)
        if (remainTime <= 0) {
            if (timer) {
                clearTimeout(timer)
                timer = null
            }
            fn()
            lastTime = nowTime
            return
        }
        //    
        if (trailing && !timer) {
            timer = setTimeout(() => {
                // console.log();
                fn()
                lastTime = !leading ? 0 : new Date().getTime()
            }, remainTime)
        }
    }
    return _throttle
}