首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >js防抖节流

js防抖节流

作者头像
hss
发布于 2022-02-25 19:20:24
发布于 2022-02-25 19:20:24
3.7K0
举报
文章被收录于专栏:前端卡卡西前端卡卡西

闭包

由于节流和防抖函数的实现都用到了闭包,所以在了解节流和防抖之前先简单介绍下什么是闭包。

代码语言:javascript
复制
function Add() {
    var x = 1;
    return function () {
        x++;
        console.log(x);
    }
}
var result = Add();  //第一次执行A函数    
result();  //2
result();  //3

上述代码:result指向函数Add返回的函数,运行完Add()后,Add的执行环境被释放,但由于函数Add里返回的函数中有对变量x的引用,因此释放时不会释放掉x,每次调用result()时,都保持对x的引用,因此x一直再增加。

防抖

定义

  • 只有在某个时间内,没有再次触发某个函数时,才真正的调用这个函数; 我们用一副图来理解一下它的过程;
  • 当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间;
  • 当事件密集触发时,函数的触发会被频繁的推迟;
  • 只有等待了一段时间也没有事件触发,才会真正的执行响应函数;

防抖函数

防抖的应用场景很多:

  • 输入框中频繁的输入内容,搜索或者提交信息;
  • 频繁的点击按钮,触发某个事件;
  • 监听浏览器滚动事件,完成某些特定操作;
  • 用户缩放浏览器的resize事件;

总之,密集的事件触发,我们只希望触发比较靠后发生的事件,就可以使用防抖函数;

在规定时间内,只让最后一次生效,前面的不生效。

最初实现

代码语言:javascript
复制
<body>
    <input type="text" class="search">

    <script>
        var timer
        function debounce() {
            if (timer) {
                clearTimeout(timer)
            }
            timer = setTimeout(function () {
                ajax()
            }, 1000);
        }

        function ajax() {
            console.log('ajaxajaxajax');
        }
        
        var search = document.querySelector('.search')
        search.addEventListener('input', debounce)
        
    </script>
</body>

上述代码是最简单的防抖函数,但是会出现如下问题: 1、对于一个页面上需要多个防抖函数的时候,需要写很多重复代码。不能复用 2、全局变量污染作用域

因此有以下升级版

实现一:优化全局变量污染

代码语言:javascript
复制
<body>
    <input type="text" class="search">

    <script>

        function debounce() {
            var timer
            return function () {
                if (timer) {
                    clearTimeout(timer)
                }
                timer = setTimeout(function () {
                    ajax()
                }, 1000);
            }

        }

        function ajax() {
            console.log('ajaxajaxajax');
        }

        var search = document.querySelector('.search')
        search.addEventListener('input', debounce())

    </script>
</body>

实现二:优化可定义要执行的函数以及防抖时间

代码语言:javascript
复制
<body>
    <input type="text" class="search">

    <script>
        // 优化可定义要执行的函数以及防抖时间
        function debounce(fn, delay) {
            var timer
            return function () {
                if (timer) {
                    clearTimeout(timer)
                }
                timer = setTimeout(function () {
                    fn()
                }, delay);
            }

        }

        function ajax(arg1, arg2) {
            console.log('ajaxajaxajax');
            // Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]
            console.log(arguments);
            console.log(this);          // window
            console.log(arg1, arg2);    // undefined undefined
        }

        var search = document.querySelector('.search')
        search.addEventListener('input', debounce(ajax, 500))

    </script>
</body>

实现三:优化this和arguments

代码语言:javascript
复制
<body>
    <input type="text" class="search">

    <script>

        // 优化this和arguments
        function debounce(fn, delay) {
            var timer
            return function () {
                if (timer) {
                    clearTimeout(timer)
                }
                var _this = this
                var _arguments = arguments
                timer = setTimeout(function () {
                    fn.apply(_this, _arguments)
                }, delay);
            }

        }

        function ajax(e, arg1, arg2) {
            console.log('ajaxajaxajax');
            // Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
            console.log(arguments);
            console.log(this);          // <input type="text" class="search">
        }

        var search = document.querySelector('.search')
        var debounceWrap = debounce(ajax, 500)


        search.addEventListener('input', function () {
            debounceWrap.apply(this, [event, 100, 200])
        })

        // 这样也可,但是要注意参数位置变化
        // Arguments(3) [100, 200, InputEvent, callee: ƒ, Symbol(Symbol.iterator): ƒ]
        // search.addEventListener('input', debounceWrap.bind(search, 100, 200))

    </script>
</body>

实现四:优化头部立即执行

代码语言:javascript
复制
<body>
    <input type="text" class="search">

    <script>
        // 优化头部立即执行
        function debounce(fn, delay, leading) {
            var timer
            var leading = leading || false
            return function () {
                if (timer) {
                    clearTimeout(timer)
                }
                var _this = this
                var _arguments = arguments
                if (leading) {
                    // 通过一个变量来记录是否立即执行
                    var isFirst = false

                    // 如果timer是fasle,则立即执行(第一次执行防抖的时候,是undefined,所以第一次一定会执行)
                    if (!timer) {
                        fn.apply(_this, _arguments)
                        isFirst = true
                    }
                    // 其余延迟执行
                    timer = setTimeout(function () {

                        // 立即执行完第一次防抖后,timer就不是fasle了,
                        // 将timer设为null,这样下一次触发防抖时,立即执行就还会生效
                        timer = null

                        // 判断在立即执行后,有没有继续触发防抖,有的话才执行,
                        // 如果只是执行了一次立即执行就结束了,则不执行
                        if (!isFirst) {
                            fn.apply(_this, _arguments)
                        }
                    }, delay);

                } else {
                    timer = setTimeout(function () {
                        fn.apply(_this, _arguments)
                    }, delay);
                }
            }

        }

        function ajax(e, arg1, arg2) {
            console.log('ajaxajaxajax');
            // Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
            console.log(arguments);
            console.log(this);  // <input type="text" class="search">
        }

        var search = document.querySelector('.search')
        var debounceWrap = debounce(ajax, 1000, true)



        search.addEventListener('input', function () {
            debounceWrap.apply(this, [event, 100, 200])
        })

        // 这样也可,但是要注意参数位置变化
        // Arguments(3) [100, 200, InputEvent, callee: ƒ, Symbol(Symbol.iterator): ƒ]
        // search.addEventListener('input', debounce(ajax, 1000, true).bind(search, 100, 200))

    </script>
</body>

实现五:优化中途取消

代码语言:javascript
复制
<body>
    <input type="text" class="search">
    <button class="cancel">取消</button>

    <script>
        // 优化头部立即执行
        function debounce(fn, delay, leading) {
            var timer
            var leading = leading || false
            var debounceFn = function () {
                if (timer) {
                    clearTimeout(timer)
                }
                var _this = this
                var _arguments = arguments

                if (leading) {
                    // 通过一个变量来记录是否立即执行
                    var isFirst = false

                    // 如果timer是fasle,则立即执行(第一次执行防抖的时候,是undefined,所以第一次一定会执行)
                    if (!timer) {
                        fn.apply(_this, arguments)
                        isFirst = true
                    }
                    // 其余延迟执行
                    timer = setTimeout(() => {

                        // 立即执行完第一次防抖后,timer就不是fasle了,
                        // 将timer设为null,这样下一次触发防抖时,立即执行就还会生效
                        timer = null

                        // 判断在立即执行后,有没有继续触发防抖,有的话才执行,
                        // 如果只是执行了一次立即执行就结束了,则不执行
                        if (!isFirst) {
                            fn.apply(_this, arguments)
                        }
                    }, delay);

                } else {
                    timer = setTimeout(() => {
                        fn.apply(_this, arguments)
                    }, delay);
                }
            }

            debounceFn.cancel = function () {
                clearTimeout(timer)
                // 清空定时器后,timer还是一个值,会导致下次立即执行失效
                // 因此清空定时器后,还要将timer设为null
                // 上面步骤不能乱,要先清空,再重新赋值null
                timer = null
            }
            return debounceFn
        }

        function ajax(e, arg1, arg2) {
            console.log('ajaxajaxajax');
            // Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
            console.log(arguments);
            console.log(this);  // <input type="text" class="search">
        }

        var search = document.querySelector('.search')
        var debounceWrap = debounce(ajax, 1000, true)


        // 注意:由于函数拥有自己的作用域,如果将防抖和中途取消分别进行定义,
        // 则他们指向的不是同一个作用域,会导致中途取消功能取消失效
        // 如果要使用中途取消功能,则必须要在外部定义一个变量保存防抖函数
        // 在执行防抖和中途取消时,都得通过外部的全局变量进行操作!
        search.addEventListener('input', function () {
            debounceWrap.apply(this, [event, 100, 200])
        })

        // 这样也可,但是要注意参数位置变化
        // Arguments(3) [100, 200, InputEvent, callee: ƒ, Symbol(Symbol.iterator): ƒ]
        // search.addEventListener('input', debounceWrap.bind(search, 100, 200))

        // 这样防抖可以,但是无法进行中途取消,错误写法!
        // search.addEventListener('input', debounce(ajax, 1000, true).bind(search, 100, 200))

        var cancel = document.querySelector('.cancel')

        cancel.addEventListener('click', function () {
            debounceWrap.cancel()
        })

        // 这样取消也可
        // cancel.addEventListener('click', debounceWrap.cancel)

        // 这样中途取消会执行,但是取消的不是同一个防抖函数,错误写法!
        // cancel.addEventListener('click', debounce(ajax, 1000, true).cancel)


    </script>
</body>

实现六:优化返回值(callback版)

代码语言:javascript
复制
<body>
    <input type="text" class="search">
    <button class="cancel">取消</button>

    <script>
        // 优化返回值
        function debounce(fn, delay, option) {
            var timer
            var option = option || {}
            var leading = option.leading || false
            var callback = option.callback || null
            var result
            var debounceFn = function () {
                if (timer) {
                    clearTimeout(timer)
                }
                var _this = this
                var _arguments = arguments
                if (leading) {
                    // 通过一个变量来记录是否立即执行
                    var isFirst = false

                    // 如果timer是fasle,则立即执行(第一次执行防抖的时候,是undefined,所以第一次一定会执行)
                    if (!timer) {
                        result = fn.apply(_this, arguments)
                        if (callback) {
                            callback(result)
                        }
                        isFirst = true
                    }
                    // 其余延迟执行
                    timer = setTimeout(() => {

                        // 立即执行完第一次防抖后,timer就不是fasle了,
                        // 将timer设为null,这样下一次触发防抖时,立即执行就还会生效
                        timer = null

                        // 判断在立即执行后,有没有继续触发防抖,有的话才执行,
                        // 如果只是执行了一次立即执行就结束了,则不执行
                        if (!isFirst) {
                            result = fn.apply(_this, arguments)
                            if (callback) {
                                callback(result)
                            }
                        }
                    }, delay);

                } else {
                    timer = setTimeout(() => {
                        result = fn.apply(_this, arguments)
                        if (callback) {
                            callback(result)
                        }
                    }, delay);
                }

            }

            debounceFn.cancel = function () {
                clearTimeout(timer)
                // 清空定时器后,timer还是一个值,会导致下次立即执行失效
                // 因此清空定时器后,还要将timer设为null
                // 上面步骤不能乱,要先清空,再重新赋值null
                timer = null
            }
            return debounceFn
        }

        function ajax(e, arg1, arg2) {
            console.log('ajaxajaxajax');
            // Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
            console.log(arguments);
            console.log(this);  // <input type="text" class="search">
            return '我是ajax的返回值'
        }

        var search = document.querySelector('.search')
        var debounceWrap = debounce(ajax, 1000, {
            leading: true,
            callback: function (res) {
                console.log(res);   // 我是ajax的返回值
            }
        })


        // 注意:由于函数拥有自己的作用域,如果将防抖和中途取消分别进行定义,
        // 则他们指向的不是同一个作用域,会导致中途取消功能取消失效
        // 如果要使用中途取消功能,则必须要在外部定义一个变量保存防抖函数
        // 在执行防抖和中途取消时,都得通过外部的全局变量进行操作!
        search.addEventListener('input', function () {
            debounceWrap.apply(this, [event, 100, 200])
        })

        var cancel = document.querySelector('.cancel')

        cancel.addEventListener('click', function () {
            debounceWrap.cancel()
        })
    </script>
</body>

实现七:优化返回值(Promise版)

代码语言:javascript
复制
<body>
    <input type="text" class="search">
    <button class="cancel">取消</button>

    <script>
        // 优化返回值
        function debounce(fn, delay, leading) {
            var timer
            var leading = leading || false
            var debounceFn = function () {
                if (timer) {
                    clearTimeout(timer)
                }
                var _this = this
                var _arguments = arguments
                return new Promise((resolve, reject) => {
                    if (leading) {
                        // 通过一个变量来记录是否立即执行
                        var isFirst = false

                        // 如果timer是fasle,则立即执行(第一次执行防抖的时候,是undefined,所以第一次一定会执行)
                        if (!timer) {
                            resolve(fn.apply(_this, _arguments))
                            isFirst = true
                        }
                        // 其余延迟执行
                        timer = setTimeout(() => {

                            // 立即执行完第一次防抖后,timer就不是fasle了,
                            // 将timer设为null,这样下一次触发防抖时,立即执行就还会生效
                            timer = null

                            // 判断在立即执行后,有没有继续触发防抖,有的话才执行,
                            // 如果只是执行了一次立即执行就结束了,则不执行
                            if (!isFirst) {
                                resolve(fn.apply(_this, _arguments))
                            }
                        }, delay);

                    } else {
                        timer = setTimeout(() => {
                            resolve(fn.apply(_this, _arguments))
                        }, delay);
                    }
                })

            }

            debounceFn.cancel = function () {
                clearTimeout(timer)
                // 清空定时器后,timer还是一个值,会导致下次立即执行失效
                // 因此清空定时器后,还要将timer设为null
                // 上面步骤不能乱,要先清空,再重新赋值null
                timer = null
            }
            return debounceFn
        }

        function ajax(e, arg1, arg2) {
            console.log('ajaxajaxajax');
            // Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
            console.log(arguments);
            console.log(this);  // <input type="text" class="search">
            return 100
        }

        var search = document.querySelector('.search')
        var debounceWrap = debounce(ajax, 1000, true)


        // 注意:由于函数拥有自己的作用域,如果将防抖和中途取消分别进行定义,
        // 则他们指向的不是同一个作用域,会导致中途取消功能取消失效
        // 如果要使用中途取消功能,则必须要在外部定义一个变量保存防抖函数
        // 在执行防抖和中途取消时,都得通过外部的全局变量进行操作!
        search.addEventListener('input', function () {
            debounceWrap.apply(this, [event, 100, 200]).then(res => {
                console.log(res);    //100
            })
        })

        var cancel = document.querySelector('.cancel')

        cancel.addEventListener('click', function () {
            debounceWrap.cancel()
        })
    </script>
</body>

防抖无注释版

代码语言:javascript
复制
<body>
    <input type="text" class="search">
    <button class="cancel">取消</button>

    <script>
        function debounce(fn, delay, leading) {
            var timer
            var leading = leading || false
            var debounceFn = function () {
                if (timer) {
                    clearTimeout(timer)
                }
                var _this = this
                var _arguments = arguments
                return new Promise((resolve, reject) => {
                    if (leading) {
                        var isFirst = false
                        if (!timer) {
                            resolve(fn.apply(_this, _arguments))
                            isFirst = true
                        }
                        timer = setTimeout(() => {
                            timer = null
                            if (!isFirst) {
                                resolve(fn.apply(_this, _arguments))
                            }
                        }, delay);
                    } else {
                        timer = setTimeout(() => {
                            resolve(fn.apply(_this, _arguments))
                        }, delay);
                    }
                })

            }

            debounceFn.cancel = function () {
                clearTimeout(timer)
                timer = null
            }
            return debounceFn
        }

        function ajax(e, arg1, arg2) {
            console.log('ajaxajaxajax');
            // Arguments(3) [InputEvent, 100, 200, callee: ƒ, Symbol(Symbol.iterator): ƒ]
            console.log(arguments);
            console.log(this);  //<input type="text" class="search">
            return 100
        }

        var search = document.querySelector('.search')
        var debounceWrap = debounce(ajax, 1000, true)


        search.addEventListener('input', function () {
            debounceWrap.apply(this, [event, 100, 200]).then(res => {
                console.log(res);   //100
            })
        })

        var cancel = document.querySelector('.cancel')

        cancel.addEventListener('click', function () {
            debounceWrap.cancel()
        })


    </script>
</body>

节流

定义

最初实现

代码语言:javascript
复制
<body>
    <input type="text" class="search">
    <script>
        function throttle(fn, interval) {
            var lastTime = 0
            return function () {
                var _this = this
                var _arguments = arguments
                var newTime = new Date().getTime()

                if (newTime - lastTime > interval) {
                    fn.apply(_this, _arguments)
                    lastTime = newTime
                }
            }
        }

        // 获取输入框
        var search = document.querySelector('.search');

        // 监听事件
        var counter = 0;
        function searchFunc(event) {
            counter++;
            console.log("发送" + counter + "次网络请求");
            console.log(this);
        };

        search.addEventListener('input', throttle(searchFunc, 1000))


    </script>
</body>

实现一:优化最后一次执行

代码语言:javascript
复制
<body>
    <input type="text" class="search">
    <script>
        function throttle(fn, interval, trailing) {
            var lastTime = 0
            var timer
            var trailing = trailing || false
            return function () {
                var _this = this
                var _arguments = arguments
                var newTime = new Date().getTime()

                clearTimeout(timer)

                if (newTime - lastTime > interval) {
                    fn.apply(_this, _arguments)
                    lastTime = newTime
                } else if (trailing) {
                    timer = setTimeout(() => {
                        fn.apply(_this, _arguments)
                    }, interval);
                }
            }
        }

        // 获取输入框
        var search = document.querySelector('.search');

        // 监听事件
        var counter = 0;
        function searchFunc(event) {
            counter++;
            console.log("发送" + counter + "次网络请求");
            console.log(this);
        };

        search.addEventListener('input', throttle(searchFunc, 1000, true))


    </script>
</body>

实现二:优化返回值(callback版)

代码语言:javascript
复制
<body>
    <input type="text" class="search">
    <script>
        function throttle(fn, interval, option) {
            var lastTime = 0
            var timer
            var option = option || {}
            var trailing = option.trailing || false
            var callback = option.callback || null
            return function () {
                var _this = this
                var _arguments = arguments
                // 获取当前最新的时间戳
                var newTime = new Date().getTime()

                // 只要触发事件就清除定时器
                if (timer) {
                    clearTimeout(timer)
                }

                var result
                if (newTime - lastTime > interval) {
                    result = fn.apply(_this, _arguments)
                    if (callback) {
                        callback(result)
                    }

                    lastTime = newTime
                } else if (trailing) {
                    timer = setTimeout(() => {
                        result = fn.apply(_this, _arguments)
                        if (callback) {
                            callback(result)
                        }
                    }, interval);
                }
            }
        }

        // 获取输入框
        var search = document.querySelector('.search');

        // 监听事件
        var counter = 0;
        function searchFunc(event) {
            counter++;
            console.log("发送" + counter + "次网络请求");
            console.log(this);
            return 100
        };

        search.addEventListener('input', throttle(searchFunc, 1000, {
            trailing: true,
            callback: function (res) {
                console.log(res);   // 100
            }
        }))

        // var funWrap = throttle(searchFunc, 1000, {
        //     trailing: true,
        //     callback: function (res) {
        //         console.log(res);
        //         return res
        //     }
        // })

        // search.addEventListener('input', function () {
        //     funWrap.call(this)
        // })


    </script>
</body>

实现三:优化返回值(Promise版)

代码语言:javascript
复制
<body>
    <input type="text" class="search">
    <script>
        function throttle(fn, interval, option) {
            var lastTime = 0
            var timer
            var option = option || {}
            var trailing = option.trailing || false
            return function () {
                var _this = this
                var _arguments = arguments
                // 获取当前最新的时间戳
                var newTime = new Date().getTime()

                // 只要触发事件就清除定时器
                if (timer) {
                    clearTimeout(timer)
                }

                var result
                return new Promise((resolve, reject) => {
                    if (newTime - lastTime > interval) {
                        result = fn.apply(_this, _arguments)
                        resolve(result)

                        lastTime = newTime
                    } else if (trailing) {
                        timer = setTimeout(() => {
                            result = fn.apply(_this, _arguments)
                            resolve(result)
                        }, interval);
                    }
                })
            }
        }

        // 获取输入框
        var search = document.querySelector('.search');

        // 监听事件
        var counter = 0;
        function searchFunc(event) {
            counter++;
            console.log("发送" + counter + "次网络请求");
            console.log(this);
            return 100
        };

        // search.addEventListener('input', throttle(searchFunc, 1000, {
        //     trailing: true,
        // }))

        var funWrap = throttle(searchFunc, 1000, {
            trailing: true,
        })

        search.addEventListener('input', function () {
            funWrap.call(this).then(res => {
                console.log(res);
            })
        })

    </script>
</body>

节流无注释版

代码语言:javascript
复制
<body>
    <input type="text" class="search">
    <script>
        function throttle(fn, interval, option) {
            var lastTime = 0
            var timer
            var option = option || {}
            var trailing = option.trailing || false
            return function () {
                var _this = this
                var _arguments = arguments
                var newTime = new Date().getTime()

                if (timer) {
                    clearTimeout(timer)
                }

                var result
                return new Promise((resolve, reject) => {
                    if (newTime - lastTime > interval) {
                        result = fn.apply(_this, _arguments)
                        resolve(result)

                        lastTime = newTime
                    } else if (trailing) {
                        timer = setTimeout(() => {
                            result = fn.apply(_this, _arguments)
                            resolve(result)
                        }, interval);
                    }
                })
            }
        }

        // 获取输入框
        var search = document.querySelector('.search');

        // 监听事件
        var counter = 0;
        function searchFunc(event) {
            counter++;
            console.log("发送" + counter + "次网络请求");
            console.log(this);
            return 100
        };

        // search.addEventListener('input', throttle(searchFunc, 1000, {
        //     trailing: true,
        // }))

        var funWrap = throttle(searchFunc, 1000, {
            trailing: true,
        })

        search.addEventListener('input', function () {
            funWrap.call(this).then(res => {
                console.log(res);
            })
        })


    </script>
</body>
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/03/07 ,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 闭包
  • 防抖
    • 定义
    • 最初实现
    • 实现一:优化全局变量污染
    • 实现二:优化可定义要执行的函数以及防抖时间
    • 实现三:优化this和arguments
    • 实现四:优化头部立即执行
    • 实现五:优化中途取消
    • 实现六:优化返回值(callback版)
    • 实现七:优化返回值(Promise版)
  • 防抖无注释版
  • 节流
    • 定义
    • 最初实现
    • 实现一:优化最后一次执行
    • 实现二:优化返回值(callback版)
    • 实现三:优化返回值(Promise版)
    • 节流无注释版
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档