js函数节流与去抖

函数节流

函数节流英文为 throttle,其翻译是「节流阀、压制、减速」,很形象地表达了它的作用就是控制函数的调用频度,但为什么需要减少函数的调用频度呢?因为某些场景下一些 DOM 事件会鬼畜般触发(如 mousemove、scroll、resize 等);或者一个抽奖页中,某个人帕金森综合征病发般点击抽奖按钮。这个时候假如事件绑定了函数的话,就会频繁调用,甚至导致某些浏览器奔溃(一种叫 IE 的浏览器)。

函数节流的原理

函数节流应用非常广,在很多源码中都会出现。其原理就是定时器。当事件触发时,通过 setTimeout 让这个事件延迟一会再执行,如果在这个时间间隔内该事件又被触发了,那就 clear 掉原来的定时器,重新 setTimeout 一个新的定时器延迟一会执行,就酱紫。上代码:

var resizeTimer = null;
$(window).on('resize', function () {
/* 第一次访问,不存在 resizeTimer,跳过这里 */
if (resizeTimer) {
    clearTimeout(resizeTimer);
}
/* 第一次访问,赋值给 resizeTimer,绑定的函数 400ms 后调用 */
resizeTimer = setTimeout(function(){
    console.log("window resize");
}, 400);
});

之后的每一次事件触发,都会反复执行 clearTimeout --> setTimeout,消除定时器的操作放在了设置定时器的操作前面,这意味着只要这个事件在 400ms 内不再触发,那么定时器就不会被消除,函数得以调用;但只要事件是在 400ms 之内触发的,函数就不能被调用,因为每次都会被消除掉,如此一来节流效果显现。

随着实践的增多,还能够发现这样做的一个好处:它能够最终确保函数得以执行。想象这样一种情景:在 iOS 系统中的 H5 页绑定了 touchmove 和 touchend 事件,你的预期是 touchmove 的时候执行一些代码,touchend 中执行收尾工作。但由于 iOS 的系统优化做得很彻底,在页面滚动的过程中会禁用所有 JavaScript 代码(这就是为什么 iOS 的滑动这么流畅),所以 touchend 是无法调用的。但如果在 touchmove 中增加了函数节流方法,那就能够确保在 touchmove 结束若干毫秒后一定能执行预期的函数。

其他版本

1.

function throttle(method, context) {
    clearTimeout(method.tId);
    method.tId = setTimeout(function(){
        method.call(context);
    }, 100);
}
window.onresize = function(){
/* 非严格模式下,若传入 undefined 或 null 给 call()/apply(),默认会传入全局对象,浏览器中为 window */
    throttle(myFunc);
}

2.

var throttle = function(fn, delay){
    var timer = null;
    return function(){
        var context = this,
        args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function(){
            fn.apply(context, args);
        }, delay);
    }
}
window.addEventListener("resize", throttle(function () {
//...
}, 250), false);


发表评论