节流器模式:多快好省

前言

节流器顾名思义就是节流,你也可以理解成截流,但它不会一棍子打死,因为它会留下一条后路,让最后一次事件得到妥善处理,而前面的都会被无情截掉。

为什么需要节流器

比如你页面中有一个返回顶部的按钮,这个按钮会根据你的页面滚动而滚动,保证自己最终会停留在页面的固定位置。

首先为了实现上面的这个带动画的返回顶部按钮,你可能会使用到 scroll 事件,不管是原生的还是第三方库(比如:jquery)的。如果你一直不停的滚动你就会发现这个返回顶部的按钮会闪动,原因是当你连续不断的滚动页面时,会一直触发 scroll 事件,并执行 scroll 事件所对应的函数(运动动画函数),这就造成了前面所说的闪动。所以我们可以使用节流器来管理这个返回顶部的按钮的动画函数。

节流器的应用除了上面对滚动的处理外,还有很多用武之地,比如:防止用户连续快速地点击一个按钮,造成函数多次执行。

节流在保存短时间内多间执行时,也节约了资源消耗,试想一下,如果在监听滚动后执行的不是一个简单的返回顶部按钮的效果,而是一个很耗性能的一些计算或者操作,那么可以想象,你的电脑轻则会出现卡顿,重则浏览器直接卡死罢工。

节流器的原理

它的原理其实很简单,通过 setTimeout 延迟执行函数,如果有再次执行的,先把上一次(如果延迟时间还没到)的迟延执行函数清掉,只保留当前这一次。

下面是一个节流器的实现代码

// 节流器
var restrictor = (function(){
    var _isType = Object.prototype.toString;
    var _shift = Array.prototype.shift;
    var options = {
        time:200
    };
    var clearTimer = function(fn){
        fn && fn.timerId && clearTimeout(fn.timerId); // 这里的 && 用法可以学习下,可以充当一些简单的 if 语句来使用。
    }
    return function(){
        var args = arguments,fn;
        if(_isType.call(args[0]) === "[object Boolean]"){ // 判断参数的类型
        // 如果是布尔值
            fn = args[1];
            clearTimer(fn);
            return;
        }
        fn = _shift.call(args); // 通过 call 把数组的方法应用到类数组(arguments)中,取出第一个参数
        if(!!!fn || _isType.call(fn) !== "[object Function]"){
        // 如果没有传参数,或者第一个参数不是函数,直接返回
            throw new Error("参数不正确,第一个参数为布尔值时第二个参数需要为函数,第一个参数为函数时,第二个及后面的参数为传入函数的参数");  // 控制台提示错误
            return;
        }
        if(args[0] || args[0] === 0){
        // 注意:这里的args[0]跟前面的args[0]是不一样的,因为它们 args 数组已经被 _shift() 方法处理过。
        // args[0] === 0 加上这个条件是为了避免排除用户自定义的零。
        	options.time = _shift.call(args);
        }
        clearTimer(fn); // 设置定时器之前,先清空传入函数所对应的定时器
        fn.timerId = setTimeout(function(){ // 把定时器的唯一标识挂在函数上可以让不同函数都可以有自己的定时器
            fn.apply(null,args); // 第一个参数为 null 时,this 默认指向 window 对象
        },options.time);
    }
})();

把节流器的功能封装成一个函数,我们以后就可以信手拈来,而不是每次都把零散的代码穿插到其它函数中。要想使用这个节流器也非常地简单,只需要往 restrictor() 方法里传入一个需要执行的函数就可以。