前端进阶(2) -- 函数节流

2019-03-29 00:00:00

函数节流的背景、原理和实现

前言

开发中经常会遇到有些函数需要被频繁调用,但是太频繁又影响性能,那怎么办呢?这个时候就需要用到函数节流了。

什么是函数节流?函数节流就是指将函数的调用时间间隔控制为 >=t。

函数节流适合需要频繁调用,但又要限制调用频率的场景:

  • mouseover / keydown
  • DOM 拖拽 等等

实现

函数节流有两种实现方式:

  • 与上一次执行时间做对比
  • setTimeout 预约执行时间

下面具体讲讲两种方式的具体实现以及背后的一些原理和逻辑

方式1:与上一次执行时间做对比

先上代码

function throttle(fn, wait) {
    let previous = 0
    return function () {
        // 此处用到了 + 运算符,后面再出一篇文章细讲该符号的一些其它用法
        let now = +new Date()
        if (now - previous < wait) return
        fn.apply(this, arguments)
        previous = now
    }
}

这种实现方式比较直观,不作过多说明

方式2:setTimeout 预约执行时间

上代码

function throttle(fn, wait) {
    let timeout = null
    return function () {
        if (timeout) return
        let args = arguments
        timeout = setTimeout(() => {
            fn.apply(this, args)
            timeout = null
        }, wait)
    }
}

先说一句setTimeout 的功能:在某个时间之后将函数加入事件循环队列。所以通过setTimeout 可以设置当前执行函数与下一函数执行间隔一定时间。

回到节流,当一直通过setTimeout 预约函数执行时,为保证预约函数执行间间隔t 时间,需要限制t 时间内不可预约,即预约函数时,设置标识,再在t 时刻执行函数时将标识取消

两种方式对比
  • 时间戳方式:马上执行,停止触发后不执行
  • setTimeout方式:非马上执行,停止触发后仍会执行一次

需求

同时满足:马上执行并且停止之后仍然执行一次。这个需求比较常遇到,在此做个分析。

先来一个大前提知识, 看下面代码:

document.getElementById('box').addEventListener('click', function () {
    var time1 = setTimeout(function () {
        console.log('time1')
        clearTimeout(time2)
    }, 1000)

    var time2 = setTimeout(function () {
        console.log('time2')
    }, 1000)
})

点击box一秒之后输出:

time1

分析一下代码:点击box之后分别向定时器线程添加两个定时任务,1000ms之后均加入事件队列,先执行time1, 在time1中通过clearTimeout 将time2清除,然后time2 不执行。这个结果说明,加入事件队列的任务可以通过clearTimeout 清除掉。

回到需求,上代码:

function throttled(fn, wait) {
    let previous = 0, timeout
    return function ()  {
        let now = +new Date()
        let distance = now - previous
        if (distance >= wait) {
            // 除了最后一个时间段需要用预约函数,其他时间段均要清除掉
            // 加入事件队列之后仍能清除,参照上面大前提分析
            if (timeout) {
                clearTimeout(timeout)
                timeout = null
            }
            fn.apply(this, arguments)
            previous = now
        } else if (!timeout) {
            // 此处实现停止之后再执行一次
            // 时间段初始就预约时间段结束时的执行函数,时间段其他时刻不再预约
            let remainTime = wait - distance
            timeout = setTimeout(() => {
                fn.apply(this, arguments)
                timeout = null
                previous = now
            }, remainTime)
        }
    }
}

总结

本文介绍了节流的两种实现方式:"时间戳" 和 "setTimeout"。到此我们就介绍了函数频繁执行的两种处理方式:函数防抖 和 函数节流。其中函数防抖属于硬预约(不管是否已预约都要重新预约),函数节流属于软预约(已预约则不再预约)。

写得头疼,摸摸头上的头发,发现还在,欣慰欣慰!劝诫大家,趁头发还在就多摸摸,别等秃了望发空流泪。

Gitalking ...

Markdown is supported

Be the first guy leaving a comment!