细说js节流函数

2,651 阅读3分钟

一、什么是节流函数

在一定时间内即使多次触发一个函数,也只执行一次。

二、什么场景需要用到节流函数

总体来说页面里面会频繁触发事件绑定的函数执行时就需要考虑使用节流函数了,具体的场景比如页面滚动(scroll)以及鼠标移动(mousemove),鼠标滚动(mousewheel),还有监听页面大小变化的resize事件。如果不节流很容易造成性能问题,导致页面卡顿。

接下说一个血淋淋的栗子,洒家最近在做一个页面全屏滚动的效果时,发现在Mac下触摸板滚动触发页面全屏滚动时发现鼠标滚动一下,页面会不停向下滑动,而不是滚动了一屏,在各种骚操作之后发现这个问题的原因在于Mac触摸板在滑动结束后,页面会有一个惯性继续滑动,继而引起页面多次滑动,据说这还是Mac的专利。尝试了几个办法失败告终,最后想到函数节流此问题得以解决。

三、节流函数的写法

1、简单版本

最简单的办法就是通过延时函数,如果没有参数,不需要考虑this指向。代码如下:

window.addEventListener('scroll', throttle(scrollFn, 1000));        
function scrollFn() {            
    console.log(new Date());        
};        
function throttle(fn, interval) {            
    var timer;            
    return function() {               
    if (timer) {                    
        return false;               
    }                
    timer = setTimeout(function() {                    
        console.log("throttle---");                    
        fn.apply();                    
        clearTimeout(timer);                    
        timer = null;                
    }, interval)}        
}   

持续滚动页面执行效果如下


如果不用接节流函数呢?

window.addEventListener('scroll', scrollFn);        
function scrollFn() {            
    console.log(new Date());        
};    


2、需要改变this指向

正真执行的函数作为回调函数传递到了节流函数中,如果按上面的代码,函数中的this是指向window对象的。如果我们有需求要把this指向特定对象,那上面的代码就去要做些小的改变,调用也要做一些变化。完整代码如下。

var data = 'outer';   
var obj = {        
    data: 'innner'    
}    
var func = throttle(scrollFn, 1000).bind(obj);//通过bind来改变this指向    
window.addEventListener('scroll', throttle(func, 1000));    
function scrollFn() {       
    console.log(new Date());        
    console.log(this.data);    
};    
function throttle(fn, interval) {        
    var timer;        
    return function() {            
        var that = this;            
        if (timer) {                
            return false;            
        }            
        timer = setTimeout(function() {               
            console.log("throttle---");                
            fn.apply(that);                
            //fn.call(that);                
            clearTimeout(timer);                
            timer = null;            
        }, interval)        
    }    
}

执行效果如下


3、需要传参

有时候我们的函数还需要传参,上面的代码就又需要做一些细微的变动了。

var data = 'outer';   
var obj = {        
    data: 'innner'    
}    
var rags = ['arg1', 'arg2'];    
var func = throttle(scrollFn, 1000).bind(obj, rags);    
window.addEventListener('scroll', throttle(func, 1000));    
function scrollFn() {        
    var args = arguments;        
    console.log(new Date());        
    console.log(this.data);        
    console.log(JSON.stringify(args));    
};    
function throttle(fn, interval) {        
    var timer;        
    return function() {            
        var that = this;            
        var args = arguments; //获取参数            
        if (timer) {                
            return false;            
        }            
        timer = setTimeout(function() {                
            console.log("throttle---");                
            fn.apply(that, args);                
            clearTimeout(timer);                
            timer = null;            
        }, interval)        
    }    
}

执行效果如下


4、第一次触发处理

上面的代码基本上达到了节流函数的效果,但有一个地方需要完善,那就是第一次触发函数的时候,我们代码都用了延时函数,每次触发都会延时处理,但实际上,页面第一次触发式一般情况下不需要做延时处理。那么我们需要对第一次触发单独加一些逻辑判断。

var data = 'outer';    
var obj = {        
    data: 'innner'    
}    
var rags = ['arg1', 'arg2'];    
var func = throttle(scrollFn, 1000).bind(obj, rags);    
window.addEventListener('scroll', throttle(func, 1000));    
function scrollFn() {        
    var args = arguments;        
    console.log(new Date());        
    console.log(this.data);        
    console.log(JSON.stringify(args));    
};    
function throttle(fn, interval) {        
    var timer;        
    var isFirstTime = true;        
    return function() {            
        var that = this;            
        var args = arguments; //获取参数            
        if (isFirstTime) {//第一次直接调用                
            fn.apply(that, args);                
            isFirst = false;            
        }            
        if (timer) {                
            return false;            
        }            
        timer = setTimeout(function() {               
            console.log("throttle---");                
            fn.apply(that, args);                 
            clearTimeout(timer);                
            timer = null;            
        }, interval||200)//给一个默认时间        
    }    
}


基本上就酱了,欢迎点赞、交流。转载注明出处。


作者:我的英文名字叫Nemo
链接:https://juejin.cn/post/6844903956242513934
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。