js节流和防抖,相信你能读懂并学会实际运用~

1,138 阅读5分钟

防抖(debounce)

定义:在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时。

用途:在进行窗口的resize、scroll,输入框内容校验或搜索等操作时,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致用户体验非常糟糕。

举例:都知道的等电梯,例如张三在6:10进入电梯,正常电梯停留5分钟即6:15关门,但在6:13时李四又按了电梯随后走进了电梯,那么电梯会在6:18正常运行。

scroll事件

当持续触发scroll事件时,事件处理函数handle只在停止滚动1000毫秒之后才会调用一次,即在持续触发scroll事件的过程中,事件处理函数handle一直没有执行。

// 防抖函数
function debounceFunc(fn, delay) {    
    let timeout = null;    
    
    return function() {        
        if(timeout) clearTimeout(timeout);        
        timeout = setTimeout(fn, delay);    
    }
}

// 处理函数
function handle() {    
    console.log(Math.random()); 
}

// 滚动事件
window.addEventListener('scroll', debounceFunc(handle, 1000));

Input搜索事件

以百度输入框为例,例如想查找“前端进阶”四个关键字,实现输完了“前端进阶”之后,再进行搜索请求,而不是每输入一个字就进行一次请求,这样可以有效减少请求次数,节约请求资源。

<body>
    <input id="name" type="text" οnkeyup="ajax">
    <script>
    // 模拟ajax请求
    function ajax(e) {
        console.log(e.target.value);
        // this.props.ajax.post('/getParams', params);
    }

    // 防抖函数
    function debounceFunc(fun, delay) {
        let timer = null;

        return function (arguments) {
            // 获取函数的作用域和变量
            let that = this;
            let args = arguments;

            // 清除定时器
            if(timer) clearTimeout(timer);

            timer = setTimeout(function () {
                // call方法第一个参数确定新的this指向,后面可以有多个参数是一串参数列表
                fun.call(that, args)
            }, delay)
        }
    }

    const name = document.getElementById('name');

    name.addEventListener('input', debounceFunc(ajax, 2000));
    </script>
</body>

节流(throttle)

定义:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数被执行,如果在同一个单位时间内某事件被触发多次,只有一次会生效。

用途:鼠标连续多次click事件,监听滚动事件,比如是否滑到底部自动加载更多。

举例:还是等电梯,例如张三在6:10进入电梯,正常电梯停留5分钟即6:15关门,6:13时李四又按了电梯随后走进了电梯,但电梯都会在6:15正常运行。

分类:函数节流主要有两种实现方法,时间戳和定时器

时间戳

当高频事件触发时,第一次会立即执行(给scroll事件绑定函数与真正触发事件的间隔一般大于delay)

而后再怎么频繁地触发事件,也都是每delay时间才执行一次

当最后一次事件触发完毕后,事件也不会再被执行了(最后一次触发事件与倒数第二次触发事件的间隔一定小于delay,因为大于的话就不叫高频事件了)

// 节流函数
function throttle(func, delay) {            
    let prev = Date.now();            
    
    return function() {                
        let context = this;                
        let args = arguments;                
        let now = Date.now();                
        
        if (now - prev >= delay) {                    
            func.apply(context, args);                    
            prev = Date.now();                
        }            
    }        
}

function handle() {            
    console.log(Math.random());        
}        

window.addEventListener('scroll', throttle(handle, 1000));

定时器

当第一次触发事件时,不会立即执行函数,而是在delay秒后才执行。

而后再怎么频繁触发事件,也都是每delay时间才执行一次。

当最后一次停止触发后,由于定时器的delay延迟,还会执行一次函数。

function throttle(func, delay) {            
    let timer = null;            
    
    return function() {                
        let context = this;               
        let args = arguments;                
        
        // 如果定时器存在,就不执行,直到delay时间后,定时器执行函数
        if (!timer) {                    
            timer = setTimeout(function() {                 
                func.apply(context, args);                   
                // 清空定时器,这样就可以设置下个定时器
                timer = null;                    
            }, delay);                
        }            
    }        
}   

function handle() {            
    console.log(Math.random());        
}    

window.addEventListener('scroll', throttle(handle, 1000));

时间戳 + 定时器

在节流函数内部使用开始时间startTime、当前时间curTime与delay来计算剩余时间remaining。

remaining<=0时表示该执行事件处理函数了(保证了第一次触发事件就能立即执行事件处理函数,且每隔delay时间执行一次事件处理函数)。

如果还没到时间的话就设定在remaining时间后再触发 (保证了最后一次触发事件后还能再执行一次事件处理函数)。

当然如果在remaining这段时间中如果又一次触发事件,那么会取消当前的计时器,并重新计算一个remaining来判断当前状态。

// 节流throttle代码(时间戳+定时器):
function throttle(func, delay) {     
    let timer = null;     
    const startTime = Date.now();     
    
    return function() {             
        const curTime = Date.now();             
        const remaining = delay - (curTime - startTime);        
        let context = this;             
        let args = arguments;             
        
        clearTimeout(timer);              
        
        if (remaining <= 0) {                    
            func.apply(context, args);                    
            startTime = Date.now();              
        } else {                    
            timer = setTimeout(func, remaining);              
        }      
    }
}

function handle() {      
    console.log(Math.random());
} 

window.addEventListener('scroll', throttle(handle, 1000));

总结

原理区分

防抖

维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,都会清除当前的计时器 重新计时。这样只有最后一次操作事件才被真正触发

节流

通过判断是否到达一定时间来触发函数,若没到规定时间则使用计时器延后,而下一次事件则会重新设定计时器。

用途区分

防抖

input搜索联想,用户在不断输入值时,即在停止输入的几秒后进行搜索,用防抖来节约请求资源。

window触发resize的时候,不断的调整浏览器窗口大小会不断的触发某个事件,用防抖来让其只触发一次。

节流

鼠标不断点击触发(例如点击提交请求按钮),用节流来控制点击或请求的次数。

监听滚动事件,比如是否滑到底部自动加载更多。

防抖 + 节流

处理搜索框,基于性能考虑,肯定不能用户每输入一个字符就发送一次搜索请求。

一种方法就是等用户停止输入,比如过了500ms用户没有再输入,那么就搜索此时的字符串,这就是防抖

节流比防抖宽松一些,比如我们希望给用户一些搜索提示,所以在用户输入过程中,每过500ms就查询一次相关字符串,这就是节流

😊 参考链接:segmentfault.com/a/119000001…

😊 刚刚加入到掘金社区,欢迎提出宝贵建议哈,争取每天推送自己整理的笔记,一起进步学习。