手写防抖节流

Posted 一腔诗意醉了酒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手写防抖节流相关的知识,希望对你有一定的参考价值。


手写前端常用技巧-防抖节流

防抖:当持续触发事件时,一定时间内没有再触发事件,才会在一段时间之后触发事件处理函数。

节流:当持续触发事件时,保证一段delay之内,只调用一次函数

防抖

  • 源代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>防抖</title>
</head>
<body>
    <label for='undebounce'>没防抖:</label>
    <input type="text" id='undebounce' onInput='onInput1(event)' >
    <br />
    <label for="debounce">有防抖:</label>
    <input type="text" id='debounce' onInput='debounceInput(event)' >

    <script>
        function debounce(fn, delay){
            let time = null
            return function(...args){
                if(time){
                    clearTimeout(time)
                }
                time = setTimeout(()=>{
                    fn.apply(this, args)
                },delay)
            }
        }
        function onInput(e){
            val = e.target.value
            if(val){
                console.log('有防抖',val)
            }
        }

        const debounceInput = debounce(onInput, 300)

        function onInput1(e){
            val = e.target.value
            if(val){
                console.log('没有防抖',val)
            }
        }
    </script>
</body>
</html>
  • 执行结果

::: tip

  • 结果分析
  1. 没有防抖的输入框持续输入时,每键入一个字母都执行一次处理函数
  2. 有防抖的输入框持续输入时,在键入字母结束之后才执行一次处理函数

:::

节流

1. 首节流

首节流,时间戳的实现,首次触发立即执行,停止触发后,没办法再次执行

  • 源代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流throttle</title>
</head>
<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
    </ul>
</body>
<style>
    body{
        display: flex;
        justify-content: center;
    }
    ul, li {
        padding:0;
        margin: 0;
    }
    li{
        width: 250px;
        height:200px;
        list-style: none;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    li:nth-child(2n){
        background:whitesmoke;
    }
    li:nth-child(2n+1){
        background:yellow;
    }
</style>
<script>
    // 首节流,时间戳的实现,停止触发后,没办法再次执行

    function throttle(fn, interval){
        let prev = 0;
        return function(...args){
            const now = Date.now();
            if( now-prev >= interval ){
                prev = now
                fn.apply(this, args)
            }
        }
    }
    function handle(){
        console.log(new Date())
    }

    const throttleHandler = throttle(handle, 3000)
    console.log('tt', throttleHandler)
    window.addEventListener('scroll', throttleHandler)
</script>
</html>

可以看到,只要滑动了,就会触发事件,但是停止滑动之后,不会触发最后一次。

2. 尾节流

定时器实现,不会立即执行,而是在delay之后执行

最后停止触发之后,还会执行一次

  • 源代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流throttle</title>
</head>
<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
    </ul>
</body>
<style>
    body{
        display: flex;
        justify-content: center;
    }
    ul, li {
        padding:0;
        margin: 0;
    }
    li{
        width: 250px;
        height:200px;
        list-style: none;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    li:nth-child(2n){
        background:whitesmoke;
    }
    li:nth-child(2n+1){
        background:yellow;
    }
</style>
<script>
    // 尾节流,定时器实现,不会立即执行,而是在delay之后执行
    // 最后停止触发之后,还会执行一次
    function throttle(fn,delay){
        let timer = null
        return function(...args){
            if( !timer ){
                timer = setTimeout(()=>{
                    fn.apply(this, args)
                    timer = null
                }, delay)
            }

        }
    }

    function handle(){
        console.log(new Date())
    }

    const throttleHandler = throttle(handle, 3000)
    console.log('tt', throttleHandler)
    window.addEventListener('scroll', throttleHandler)
</script>
</html>

可以看到我们停止滑动之后还是触发了事件

3. 首尾节流

  • 源代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>节流throttle</title>
</head>
<body>
    <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
        <li>6</li>
        <li>7</li>
        <li>8</li>
        <li>9</li>
    </ul>
</body>
<style>
    body{
        display: flex;
        justify-content: center;
    }
    ul, li {
        padding:0;
        margin: 0;
    }
    li{
        width: 250px;
        height:200px;
        list-style: none;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    li:nth-child(2n){
        background:whitesmoke;
    }
    li:nth-child(2n+1){
        background:yellow;
    }
</style>
<script>
    // 首尾节流,计时器+时间戳
    function throttle(fn, delay){
        let timer = null
        let startTime = 0
        return function(...args){
            let curTime = Date.now();
            let remaining = delay - (curTime - startTime)
            // 清除旧的计时器
            clearTimeout(timer)

            if(remaining <= 0 ){
                fn.apply(this, args)
                startTime = Date.now()
            }else{
                timer = setTimeout(()=>{
                    fn.apply(this, args)
                    startTime = Date.now()
                }, remaining)
            }

        }
    }


    function handle(){
        console.log(new Date())
    }

    const throttleHandler = throttle(handle, 500)
    console.log('tt', throttleHandler)
    window.addEventListener('scroll', throttleHandler)
</script>
</html>

在这里我们缩短节流时间,方便看效果。

可以看到每次滑动都至少触发两次事件,即首尾触发

总结

虽然防抖和节流都是避免同一时间频繁执行处理函数,但是原理却有一些差别

::: note

应用场景

  • 防抖 debounce

    • search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
    • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
  • 节流 throttle

    • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
    • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

:::

文章详细信息,请查看个人博客

以上是关于手写防抖节流的主要内容,如果未能解决你的问题,请参考以下文章

JS手写面试题 --- 防抖节流

☀️七分钟学会手写防抖和节流

☀️七分钟学会手写防抖和节流

手写防抖、节流 hook(ts版)

面试官说手写 :防抖和节流

手写防抖节流函数