函数防抖和函数节流


title: 函数防抖和函数节流
data: 2020/05/17

温故而知新!—— 《论语》

函数防抖和函数节流

概念

函数防抖(debounce): 事件在触发后的 t 时刻执行,如果在这个时间间隔 t 内,又一次触发事件,则重新计算时间。

函数节流(throttle): 在时间间隔 t 内,无论触发多少次事件,最终只执行一次。

最简单的函数防抖

通过能直接运行的代码一步步深入了解什么是 函数防抖

示例

<!-- index.html - 函数防抖样例 -->
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="utf-8">
    <meta name="author" content="xiangchengyu">
</head>
<body>
    <button id="js_debounce">防抖</button>

    <script type="application/javascript">
    !(function (global) {
        let element = {
            debounce: document.getElementById('js_debounce')
        };

        element.debounce.addEventListener('click', debounce(onClick, 2000));

        function onClick(event) {
            console.log(event);
        }

        /** 函数防抖 */
        function debounce (fn, delay)
        {
            console.log("我被执行了!"); // ① 我在什么时候执行?
            let timer = null;
            return function() {
                let params = arguments;
                clearTimeout(timer);
                timer = setTimeout(function() {
                    fn && fn.apply(this, params);
                }, delay);
            };
        }
    })(this);
    </script>
</body>
</html>

代码分析

在做代码分析前,我请先思考下面这个问题:

  1. 代码 ① 在什么时候被执行?

错误答案每次 debounce 按钮点击后立即执行

如果你陷入这个误区很容易认为上面的函数防抖代码有问题,你可能会疑惑:这样的话,每次点击不都会调用 debounce 函数吗?那么 let timer = null 后又 clearTimeout(timer) 有什么意义呢?

其实:

/*
`debounce(onClick, 2000)` 是一个完整的函数表达式,即函数调用。这里 `debounce(onClick, 2000)` 在绑定 `click` 事件时会立即执行并返回一个函数,
每当我们触发点击事件的时候执行的是 `debounce(fn, 2000)` 返回的匿名函数。
*/
element.debounce.addEventListener('click', debounce(onClick, 2000));

// 提供对比的代码1
element.debounce.addEventListener('click', onClick);

// 提供对比的代码2
element.debounce.addEventListener('click', onClick());

所以,代码 ① 在绑定 click 事件后立即执行,并非每次点击都会触发执行。

代码分析

// __函数防抖(debounce):__ 事件在触发后的 t 时刻执行,
// 如果在这个时间间隔 t 内,又一次触发事件,则重新计算时间。

function debounce(fn, delay) { // ==> 函数作用域 ①
    let timer = null; // 属于函数作用域 ① 的变量
    return function() { // ==> 函数作用域 ②
        let params = arguments; // 匿名函数 ② 的参数(获取被防抖函数的参数)
        clearTimeout(timer); // 清除函数作用域 ① 的 timer
        timer = setTimeout(function () { // ==> 函数作用域 ③,改变函数作用域 ② 中 timer 的值
            fn && fn.apply(this, params); // params 从函数作用域 ② 获得
            // 使用 apply 可以避免使用 `...` 扩展 扩展运算符,有一定的兼容性哦!
            // fn && fn.call(this, ...arguments);
        }, delay);
    }
}

// 需要被防抖的函数
function fn(str) {
    console.log(" ====> " + str);
}

// 得到匿名函数 ②
let debounceFn = debounce(fn, 3000);

// 执行防抖函数
debounceFn('Hello World!');

实战一 单击是单击,双击是双击(dblclick is not twice click)

我们都知道,双击其实就是两次单击的组合事件。但大部分时候,我们并不希望在双击的时候同时触发单击事件。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <button id="js_button">别说话,吻我</button>

    <script>
        let btn = document.getElementById('js_button');
        
        // ① 快速的多次点击按钮(超过三次且快速),观察输出结果。
        btn.addEventListener('click', handleClick);
        btn.addEventListener('dblclick', handleDoubleClick);

        function handleClick(event) {
            console.log("请再爱我一次!");
        }

        function handleDoubleClick(event) {
            console.log("喜欢是追求,爱是克制!");
        }
    </script>
</body>
</html>

运行上面的代码,你会发现双击按钮也会触发单击的事件。现在的需求是:单击触发 请再爱我一次!,双击只触发 喜欢是追求,爱是克制!

我发现: ① 快速且多次的点击按钮,双击事件只会触发一次(只会在第二次单击后执行,oh,double click is strict twice click!),单击事件则会每次都触发。

下面我们来增加一些代码来满足我们的需求:

<!-- 函数防抖 - 实战一、单击是单击,双击是双击 -->
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <button id="js_button">别说话,点我</button>

    <script>
        let btn = document.getElementById('js_button');

        let debounceFn = debounce(dispatcher, 600);

        btn.addEventListener('click', debounceFn);
        btn.addEventListener('dblclick', debounceFn);

        /* 事件分发器 */
        function dispatcher(event) {
            switch (event.type) {
                case 'click':
                    handleClick(event);
                    break;
                case 'dblclick':
                    handleDoubleClick(event);
                    break;
                default:
                    console.warn('unknown event type');
                    break;
            }
        }
        
        /* 单击事件处理函数 */
        function handleClick(event) {
            console.log("请再爱我一次!");
        }

        /* 双击事件处理函数 */
        function handleDoubleClick(event) {
            console.log("喜欢是追求,爱是克制!");
        }

        /* 防抖函数 */
        function debounce(fn, delay) {
            let timer = null;
            return function() {
                let params = arguments;
                clearTimeout(timer);
                timer = setTimeout(() => {
                    fn && fn.apply(this, params);
                }, delay);
            };
        }
    </script>
</body>
</html>

测试运行,完美实现需求。

可立即执行防抖函数

“为啥你这第一次点击时感觉跟卡顿了一样?能不能立即执行?”客户如是说。不得不说,有的时候客户提的需求虽然刁钻但又很合理。做函数防抖本就为了防止用户频繁操作触发事件,如果因为代码执行慢而导致用户疯狂点击岂不得不偿失、事与愿违?

/* 函数防抖 - 立即执行的防抖函数 */
function debounce(fn, delay, immediate) {
    let timer = null;
    let _immediate = immediate;
    return function() {
        let params = arguments;
        if (_immediate && immediate) {
            fn && fn.apply(this, params);
            _immediate = false;
        }
        clearTimeout(timer);
        timer = setTimeout(function() {
            (_immediate || !immediate) && fn && fn.apply(this, params);
            _immediate = true;
        }, delay);
    }
}

可取消执行的防抖函数

大家应该都玩过 QQ 吧!QQ 有一个功能是光标悬浮在好友头像上,主面板周围会弹出一个信息面板。如果光标快速在多个好友头像经过,该信息面板不会显示,只有光标在头像上悬浮一会儿后或光标停留在信息面板上,信息面板才会展示出来。(建议大家先去体验一番哦,体验完之后先思考你会如何实现~)

2020-11-09_014100

假设我现在想实现类似的功能,我的思路是下面这样子:

  1. 写一个防抖函数,避免光标快速经过头像后频繁触发显示信息面板事件;
  2. 光标 mouseenter 到头像时执行防抖函数,实现光标悬浮一会儿后展示信息面板;
  3. 假设光标悬浮时间较短,则 取消执行 展示信息面板 的方法,即完善步骤 1 的防抖函数。
  4. 假设信息面板已经展示出来,光标从头像移动至信息面板的过程中(光标移出头像且并未移入面板)执行 隐藏信息面板 的方法。如果光标从头像移动至信息面板的过程很短(信息面板还没隐藏),则 取消执行 隐藏信息面板 的方法

可取消的防抖函数比较简单,只需要添加一个方法去清除定时器即可。代码片段如下:

/* 函数防抖 - 可取消的防抖函数 */
function debounce(fn, delay) {
    let timer = null;
    
    debounce.cancel = function() {
        clearTimeout(timer);
    }

    return function() {
        let params = arguments;
        clearTimeout(timer);
        timer = setTimeout(function() {
            fn && fn.apply(this, params);
        }, delay);
    }
}

实战二 QQ的逻辑也不是那么复杂嘛

下面的代码实现了上述的需求。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <style>
        .avatar {
            width: 60px;
            height: 60px;
            background-color: gray;
            text-align: center;
            line-height: 60px;
        }
        .circle {
            border-radius: 50%;
        }

        .panel {
            display: none;
            width: 100px;
            height: 100px;
            margin-top: 20px;
            background-color: red;
        }
    </style>
</head>
<body>
    <div id="js_avatar" class="avatar circle"></div>

    <div id="js_panel" class="panel">面板</div>

    <script>
        let isShowPanel = false;

        let avatarElem = document.getElementById('js_avatar');
        let panelElem = document.getElementById('js_panel');

        let debounceFn = debounce(dispatcher, 1000, false);

        avatarElem.addEventListener('mouseenter', debounceFn);
        avatarElem.addEventListener('mouseleave', debounceFn);
        panelElem.addEventListener('mouseenter', debounceFn);
        panelElem.addEventListener('mouseleave', debounceFn);

        /* 事件分发器 */
        function dispatcher(event) {
            switch (event.type) {
                case 'mouseenter':
                    handleMouseEnter(event);
                    break;
                case 'mouseleave':
                    handleMouseLeave(event);
                    break;
                default:
                    console.warn('unknown event type');
                    break;
            }
        }

        function showPanel() {
            console.log("showPanel 我的逻辑被执行了");
            panelElem.style.display = 'block';
            isShowPanel = true;
        }

        function hidePanel() {
            console.log("hidePanel 我的逻辑被执行了");
            panelElem.style.display = 'none';
            isShowPanel = false;
        }
        
        /* 单击事件处理函数 */
        function handleMouseEnter(event) {
            // showPanel(); // 不取消防抖,每次都会执行哦!
            // 取消隐藏
            isShowPanel ? debounce.cancel() : showPanel();
        }

        /* 双击事件处理函数 */
        function handleMouseLeave(event) {
            // hidePanel(); // 不取消防抖,每次都会执行哦!
            // 取消显示
            !isShowPanel ? debounce.cancel() : hidePanel();
        }

        /* 防抖函数 */
        function debounce(fn, delay, immediate) {
            let timer = null;
            let _immediate = immediate;
            
            debounce.cancel = function() {
                clearTimeout(timer);
            }

            return function() {
                let params = arguments;
                if (_immediate && immediate) {
                    fn && fn.apply(this, params);
                    _immediate = false;
                }
                clearTimeout(timer);
                timer = setTimeout(function() {
                    (_immediate || !immediate) && fn && fn.apply(this, params);
                    _immediate = true;
                }, delay);
            }
        }
    </script>
</body>
</html>

函数节流

函数节流(throttle): 在时间间隔 t 内,无论触发多少次事件,最终只执行一次。

最简单的函数节流

function throttle(fn, duration) {
    let previous = 0;
    return function() {
        let params = arguments;
        if (new Date().getTime() - previous < duration) {
            return;
        }
        fn && fn.apply(this, params);
        previous = new Date().getTime();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
微信小程序中的函数防抖函数节流是两种用于控制函数触发频率的技术。 函数防抖是延迟函数执行,并且不管触发多少次都只执行最后一次。这意味着在一段时间内多次触发函数时,只有最后一次触发会真正执行函数函数防抖的实现方式是,在函数被触发时,设置一个定时器,在定时器的时间间隔内,如果函数再次被触发,就重新设置定时器。只有当定时器时间间隔结束后,才真正执行函数。 [2] 函数节流是减少函数的触发频率,即控制函数在一段时间内只能执行一次。函数节流的实现方式是在函数被触发时,首先判断当前时间与上次触发函数的时间间隔是否超过了设定的时间间隔,如果超过了设定的时间间隔,则执行函数并更新上次触发函数的时间。如果没有超过设定的时间间隔,则不执行函数。这样可以有效控制函数的执行频率。 [2] 总结起来,函数防抖是延迟函数执行,只执行最后一次触发,而函数节流是减少函数的触发频率,控制函数在一段时间内只能执行一次。在微信小程序中,可以根据具体的需求选择适合的技术来控制函数的触发频率。 [2 [3<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [微信小程序 - 函数防抖 / 函数节流(轮子封装)](https://blog.csdn.net/weixin_44198965/article/details/107834833)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [微信小程序之使用函数防抖函数节流](https://blog.csdn.net/weixin_30709929/article/details/97002298)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值