JavaScript函数节流(throttle)与函数去抖(debounce)

对于浏览器窗口大小改变的时候,来动态改变页面元素的大小,可以采用window的resize事件,实现代码:

<script type="text/javascript">
    var n = 0;
    function resizehandler(){
        console.log(new Date().getTime());
        console.log(++n);
    }
    
    window.onresize = resizehandler;
</script>

功能能够实现,都是当我们用拖拽的方式改变浏览器大小的时候,控制台会不断打印执行resizehandler的函数的结果。

一次简单的拖拽会让resizehandler()函数执行很多次,实际在显示项目中resizehandler函数可能会很复杂,甚至会涉及到前后端的数据交互,所以一次拖拽执行很多次很明显是不能够接受的。

函数去抖

其实我们的本意只是窗口resize后页面做一些调整就可以了,而window的resize事件并不是在resize结束后才出发,具体的触发频率不是很清楚,但却在不停地调用,直到窗口大小不在变化。类似的机制还有鼠标的mousemove,都是在短时间内重复触发。

在《JavaScript高级程序设计》中有专门应对此问题的函数防抖

function throttle(method, context){
    clearTimeout(method.tId);
    method.tId = setTimeout(function(){
        method.call(context);
    }, 500);
}

原理很简单,利用定时器,让函数执行延迟500毫秒,在500毫秒内如果有函数又被调用则删除上一次的调用,这次调用500毫秒后执行,如此往复。这样刚才的代码可以改为:

<script type="text/javascript">
    var n = 0;
    function resizehandler(){
        console.log(new Date().getTime());
        console.log(++n);
    }
    
    function throttle(method, context){
        clearTimeout(method.tId);
        method.tId = setTimeout(function(){
           method.call(context);
        }, 500);
    }
    
    window.onresize = function(){
        throttle(resizehandler, window);
    };
</script>

这样的话执行就没有问题了。

函数防抖的另一种方法

预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个周期。

function throttle(method, dalay){
    var timer = null;
    return function(){
        var context = this, args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function(){
            method.apply(context, args);
        }, delay);
    }
}

调用一下试试,一样的效果

<script type="text/javascript">
    var n = 0;
    function resizehandler(){
        console.log(new Date().getTime());
        console.log(++n);
    }
    
    function throttle(method, delay){
        var timer = null;
        return function(){
            var context = this, args = arguments;
            clearTimeout(timer);
            timer = setTimeout(function(){
                method.apply(context, args);
            }, delay);
        }
    }
    
    window.onresize = throttle(resizehandler, 500); //这里因为返回函数句柄,不用包装函数了。
</script>

比较

两种方法都是利用了setTimeout,不同的是第二种方法加入的函数延迟执行时间,这个在第一种方案中很容易也具有的功能,无非是加一个参数。

但是第一种方案把tId设为函数的一个变量保存,而第二种创建了闭包存储。个人觉得差距不大,很喜欢第一种,简单,高效。

新需求

百度首页输入自动提示一样的东西,我在text上绑定keyup事件,每次键盘弹起的时候自动提示,但是又不想提示那么频繁,于是我用了上面方法,但是悲剧了,只有挺直输入等500毫秒才会提示,在输入过程中根本就没有提示。看了一下代码,可不是嘛,只有用户会盲打,在500毫秒内按一下键盘,提示函数就会不断被延迟,这样只有停下来的时候才会提示,这就没有意义了。

能不能在函数节流的基础上间隔固定时间就执行一次?

函数节流

在网上搜了一下我们可以根据第二种下发(第一种函数拓展多个变量感觉有些不好)做些改动,添加一个参数作为到固定间隔必须执行。

function throttle(method, delay, duration){
    var timer = null, begin = new Date();
    return function(){
        var context = this, args = arguments, current = new Date();
        clearTimeout(timer);
        if(current-begin >= duration){
            method.apply(context, args);
            begin = current;
        } else {
            timer = setTimeout(function(){
                method.apply(context, args);
            }, delay);
        }
    }
}

这样每次我们判断间隔了够久,要是超过设置时间则立即执行一次,以刚才的例子试一试效果

window.onresize = throttle(resizehandler, 100, 200);

这样,既没有频繁执行也没有就最后执行。

总结

对于函数节流在做动态响应用户行为方面有较大的使用频率,具体使用基础版本的函数节流还是改动版本的还要根据业务场景进行具体分析。

throttle和debounce均是通过减少实际逻辑处理过程的执行来提高事件处理函数运行性能的手段,并没有实质上减少事件的触发次数。 (逃)

image

posted @ 2017-12-12 13:52  淡烘糕  阅读(1315)  评论(0编辑  收藏  举报