一文搞定JavaScript的防抖与节流的理解与封装

553 阅读3分钟

防抖与节流

这是我参与更文挑战的第5天,活动详情查看更文挑战

为什么使用防抖节流?
在前端开发中有一部分的用户行为会频繁的触发事件执行,而对于DOM操作、资源加载等耗费性能的处理,很可能导致界面卡顿,甚至浏览器的崩溃。函数节流(throttle)和函数防抖(debounce)就是为了解决类似需求应运而生的。

防抖(debounce)

函数防抖就是在函数需要频繁触发情况时,只有足够空闲的时间,才执行一次。好像公交司机会等人都上车后才关门一样。他不会上来一个人就触发一次关门,而是等人陆续上来后等待一会再触发关门

场景:(当时间频繁触发后停止一段时间执行)

  • 实时搜索(keyup)
  • 拖拽( mousemove )
  • 手机号、邮箱验证输入检测
  • 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。

我们以百度搜索框在输入完成后停顿一段时间便会进ajax请求为例子,我们实现输入框停止输入后再打印

//使用高阶函数,利用闭包封装
    <!-- <script src="../../plugin/helpers.js"></script> -->
   
        let oInp = document.getElementById('inp')
        let timer = null
        const debounce = (handler, delay) => {
            let timer = null //利用闭包保存同一个timer
            return () => {
                let _self = this //取debounce执行作用域的this
                let _arg = arguments //利用闭包保存参数数组
                clearTimeout(timer) //不断的执行函数不断的清除定时器
                timer = setTimeout(() => {
                    handler.apply(_self, _arg) //用apply指向调用debounce的对象,相当于_this.handler(args);
                }, delay)
            }
        }
        let shouValue = (e) => {
            console.log(e,this.value)
        }
        oInp.oninput = debounce(shouValue, 1000);

节流(throttle)

函数节流就是预定一个函数只有在大于等于执行周期时才执行,周期内调用不执行。好像水滴攒到一定重量才会落下一样。

场景: CON(不管时间触发的多么频繁,至少要每隔一段时间执行而不是等事件触发完了等一段时间执行)

窗口调整(resize)
页面滚动( scroll)
抢购疯狂点击( mousedown)

目前我有两种思路:

  • 使用定时器,定时一秒钟之后去执行,但是在这1s中不停的调用,不让他的定时器清零重新计时,不会影响当前的结果,还是那1s继续等,等1秒时触发(会出现停止操作还是会触发)
//保证一个时间段内执行一次
        const throttle = (handler, time) => {
            let timer
            return () => {
                if (timer) {
                    return //判断如果有计时器不清零直接返回啥也不做
                }
                let _self = this //取throttle 执行作用域的this
                let _arg = arguments //利用闭包保存参数数组
                timer = setTimeout(() => {
                    handle.apply(_self, _arg)
                    timer = null
                }, time)
            }
        }
        //触发事件
        window.onresize =  () => handle()
        //处理函数
        let test = () =>console.log("a")
        //调用throttle函数传参
        let handle = myPlugin.throttle(test, 2000)

  • 使用时间戳,先立即执行,只不过在下一次执行要等一段时间
 const throttle = (handler, time) => {
            let t 
            return () => {
                let _self = this //取throttle 执行作用域的this
                let _arg = arguments //利用闭包保存参数数组
                if (!t || Date.now() - t >= time ) {
                    handler.apply(_self , _arg );
                    t = Date.now(); //得到的当前时间戳
                }
            }
        }
        window.onresize =  () => handle()
        let test = () =>console.log("a")
        let handle = myPlugin.throttle(test, 2000)

判断两种方式差异,我们只需要判断是否需要立即执行,我们就可以把节流的两种合并在一起

//保证一段时间只执行一次
      constthrottle = (handler, time, immediately) => {
            if (immediately === undefined) {
                immediately = true //判断需要先立即执行
            }
            if (immediately) {
                let t
                return () => {
                    let _self = this //取throttle 执行作用域的this
                    let _arg = arguments //利用闭包保存参数数组
                    if (!t || Date.now() - t >= time) {
                        handler.apply(_self, _arg);
                        t = Date.now(); //得到的当前时间戳
                    }
                }
            }
            else {
                let timer
                return () => {
                    if (timer) {
                        return //判断如果有计时器不清零直接返回啥也不做
                    }
                    let _self = this //取throttle 执行作用域的this
                    let _arg = arguments //利用闭包保存参数数组
                    timer = setTimeout(() => {
                        handle.apply(_self, _arg)
                        timer = null
                    }, time)
                }
            }

        }
        window.onresize = () => handle()
        let test = () => console.log("a")
        let handle = myPlugin.throttle(test, 2000,true)