从闭包开始理解防抖

58 阅读2分钟

1. 场景,是前端做性能优化限制事件触发频率的一种常用手段,比如控制搜索词条的触发频率,也是面试常考题。
2. 概念:事件只在触发的n秒内未再次触发才执行,否则不执行。
3. 实现:从概念来理解,要n秒后才触发这里需要用到定时器setTimeout, n秒内再次触发则不执行这里需要清除每次触发生成的定时器。因为每次都会触发事件,所以需要一个变量存储每次触发的定时器作更新,该变量要在事件触发过程中长期使用,所以考虑用闭包来读取这个变量。

<!DOCTYPE html>
<html lang="zh-cmn-Hans">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
    <title>debounce</title>
    <style>
        #container{
            width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
        }
    </style>
</head>

<body>
    <div id="container"></div>
    <script>
        var count = 1;
        var container = document.getElementById('container');
        function getUserAction() {
            container.innerHTML = count++;
        };
        container.onmousemove = getUserAction;
    </script>
</body>

</html>

以上代码表明div盒子中鼠标移动多少次就执行多少次数字累加,频率非常快。

根据定义实现

function debounce(func, delay) {
    let timeout = null;
    return function(){
        clearTimeout(timeout);
        timeout = setTimeout(() => {
             func.apply(this, arguments)
         }, delay)
    }
}

调用

container.onmousemove = debounce(getUserAction, 1000);

触发onmousemove事件,返回匿名函数执行,先清除上一次触发的定时器,同时将新的定时器存储到timeout中,在delay毫秒后执行累加事件,如果再次触发了onmousemove则会再次执行匿名函数清除上次的定时器,并设置新的定时器存到timeout中,delay毫秒后执行。这里的关键点是onmousemove执行的是返回的匿名函数(闭包),将局部变量timeout一直能够持续读取并更新。

加上立即执行参数

function debounce(func, delay, immediate) {
            let timeout = null;
            return function () {
                clearTimeout(timeout)
                if (immediate) {
                   // callNow为是立即执行的参数
                    let callNow = !timeout;
                    console.log(timeout, 'timeout')
                    timeout = setTimeout(() => {
                        timeout = null;
                    }, delay)
                    if (callNow) {
                        func.apply(this.arguments)
                    }
                } else {
                    timeout = setTimeout(() => {
                        func.apply(this, arguments)
                    }, delay)
                }
            }
        }

是否立即执行的这段理解,callNow是否立即执行参数,设置定时器在delay毫秒后重新设置timeout为null,只有timeout为null才能执行,因此初次触发后,后面delay毫秒内每次触发事件返回的闭包内对timeout的辅助都是新的定时器,所以callNow必为false,执行不了