节流和防抖

190 阅读2分钟

为什么要使用 节流 & 防抖 呢

最主要的作用当然是用于提升性能。虽然大部分情况不使用他们代码也能跑,程序一样动,但是如搜索提示功能,在没有运用节流的情况下,用户一直打字,你的代码就会一直请求ajax,network上刷出一片的接口请求,会对服务器端造成极大的压力。用节流优化后,能大幅度的减少服务器的负载,减轻压力

节流(throttle)

节流是什么呢

概念性的讲就是:一个事件只能在间隔一段时间内最多生效一次,不管你在间隔时间内触发了多少次,也就只有第一次生效

大佬的理解:函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹

使用场景

监听滚动条滚动事件

具体实现

有两种比较常见的解决办法:

定时器写法

// 节流
const throttle = function() {
    let args = [...arguments],
        timeout = args[0],
        fns = args.slice(1), // 回调函数数组,当你是自定义事件时可以传入多个方法进行触发
        canRun = true,
        timer;
    return function() {
        if (canRun) {
            timer = setTimeout(() => {
                canRun = true
            }, timeout);
            for (const fn of fns) {
                fn.call(this, ...arguments)
            }
            canRun = false
        }
    }
}

window.onscroll = throttle(1000, function () {
    let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    console.log(`滚动距离:${scrollTop}`);
})

时间戳写法

// 节流
const throttle = function() {
    let args = [...arguments],
        timeout = args[0],
        fns = args.slice(1), // 回调函数数组,当你是自定义事件时可以传入多个方法进行触发
        before;
    return function() {
        let now = new Date()
        before = before || new Date()

        if (before && now - before >= timeout) {
            for (const fn of fns) {
                fn.call(this, ...arguments)
            }
            before = now
        }
    }
}

window.onscroll = throttle(1000, function () {
    let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    console.log(`滚动距离:${scrollTop} ${new Date()}`);
})

防抖(debounce)

防抖又是什么呢

概念性的讲就是:一个事件只能在上一次触发一段时间后,才能继续触发,如果提前触发就重新计时

大佬的理解:函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条

使用场景

京东搜索提示功能
在京东的搜索框中,当你一直输入的时候,他是不会去请求接口去查找你可能需要的商品的,只有你停止输入了之后才会去请求,这里就可以运用防抖实现类似的优化

具体实现

<body>
    <input type="text">
    <button>点击</button>
</body>
<script>
    // 防抖
    const debounce = function () {
        let args = [...arguments],
            timeout = args[0],
            fns = args.slice(1), // 回调函数数组,当你是自定义事件时可以传入多个方法进行触发
            timer;

        return function () {
            clearTimeout(timer)
            timer = setTimeout(() => {
                for (const fn of fns) {
                    fn.call(this, ...arguments)
                }
            }, timeout);
        }
    }

    var inputGetAjax = debounce(1000, function (val) {
        console.log(`输入框此时文字:${this.value}`);
    })
    var btnGetAjax = debounce(1000, function (val) {
        this.innerText = '我被执行了'
        console.log('按钮被执行了');
    })

    const input = document.querySelector('input')
    const btn = document.querySelector('button')
    input.addEventListener('input', inputGetAjax)
    btn.addEventListener('click', btnGetAjax)
</script>

但是这种写法第一次触发的时候是不会立即触发的,而是会等待计时器计时完毕才会执行回调函数。

如果我想第一次触发马上就执行一次该怎么做呢

只需要加上一个判断变量就可以了

    // 省略相同代码...
    
    // 防抖
    const debounce = function () {
        let args = [...arguments],
            timeout = args[0],
            fns = args.slice(1), // 回调函数数组,当你是自定义事件时可以传入多个方法进行触发
            canRun = true, // <--- 新增的地方
            timer;

        return function () {
            if (canRun) { // <--- 新增的地方
                for (const fn of fns) {
                    fn.call(this, ...arguments)
                }
                canRun = false
            } else { // <--- 新增的地方 👇
                clearTimeout(timer)
                timer = setTimeout(() => {
                    canRun = true
                }, timeout);
            } // <--- 新增的地方 👆
        }
    }

    // 省略相同代码...

归纳

想固定频率执行的用节流,不想进行持续操作的时候用防抖。

相关链接

完全看不懂我写什么的可以去拜读大佬的文章

7分钟理解JS的节流、防抖及使用场景