如何写出自己的underscore防抖

527 阅读5分钟

前言

为什么我们要在项目中使用防抖???

随着计算机的不断发展,浏览器也在飞速的更新迭代。从刚开始的只是展示图文排版的网页,到现在人机交互和科学计算的动态项目。不同的时代同样也推动着思想的发展,到目前为止的网页,直面用户最多的也就是网站的视觉特效和交互性能。性能提升更是作为前端工程师都要关注的一个问题。防抖作为代码层面的提升性能的工具是很有必要学一下的。

防抖能做的事

在前端开发中会遇到一些频繁的事件触发,比如

  1. window的resize、scroll事件等
  2. mousedown、mousemove事件
  3. 搜索框的input事件等

上面这些事件频繁的触发会引起页面的抖动,如果涉及到请求,同样也会发生请求爆炸。在前端社会需要的有人来站出来拯救这个战乱的时候,防抖来了。

防抖的实现原理

你尽管触发事件,但是我一定在事件触发n秒后才执行,如果你在一个事件触发的n秒内又触发了这个事件,那么我就以新的事件的时间为准,n秒后再执行。总之,就是要等这个事件触发完n秒后再触发事件。

underscore防抖的实现

防抖就不一步一步的实现了,有需求的话请欣赏冴羽大大的 跟着underscore学防抖文章,每一步的实现讲的非常清晰,同时也感谢冴羽大大让我又明白了一项技术。

实现防抖的注意点。

  1. 由于是事件处理,debounce函数必须要接受一个函数作为参数,并返回一个函数,第二个参数为触发事件所等待的时间。其次,既然等待就必须要有定时器,当每次触发事件的时候首先要清除定时器。
  2. 当绑定时间处理的时候,此时的this应该是事件源本身。
  3. event事件对象:当我们触发事件的时候,相应的我们会得到event事件对象。
  4. 立即执行,有时候我们想当我们首次触发事件的时候会立即执行,以后连续的操作会出现防抖操作。立即执行我们的防抖函数要加上第三个参数判断是否要立即执行。
  5. 返回值:如果我们的函数中是有返回值的,当immediate为false的时候,因为使用了setTimeout,最后return的时候一直是undefined,所以,只在immediate为true的时候返回值。
  6. 取消:有时候我们需要这样的一个需求,当我的防抖时间间隔很长的时候。在这个漫长的等待过程中,我想要取消防抖。再次执行然后触发。

根据上面这些实现防抖的注意点,我们的underscore防抖诞生了。

function debounce(fn,time,immediate) {
    var  timeout,result;
    var debounced =  function (){
        var context = this;
        var args = Array.prototype.slice.call(arguments);

        if(timeout){
            clearTimeout(timeout);
        }
        if(immediate){
            var callNow = !timeout;

            timeout = setTimeout(function(){
                timeout = null;
            },time)
            if(callNow){
                result = fn.apply(context,args);
            }
        } else{
            timeout = setTimeout(function(){
                fn.apply(context,args);
            },time)
        }
        return result;
    }
    debounced.cancel = function(){
        clearTimeout(timeout);
        timeout = null;
    }
    return debounced;
}

说说我碰到的问题

立即执行

underscore防抖的立即执行的功能很简单的可以看出避免用户等候的时间太长,所做的优化,但是会发现当频繁触发事件的时候,立即执行了一次,后续就不会触发第二次。在鼠标移动事件的可以很完美,但是到搜索框上就不那么美妙了。

右边的数字是显示input触发的次数,这就可以看出,我按了那么多,它只执行了开始的一次,最后的一次并没有执行。

个人观点

在立即执行这个功能上,存在着不同需求的原因,根据不同的需求去选择合适的功能。比如在鼠标划动上可以添加上立即触发功能而放弃最后一次的触发,在搜索框上面则不需要立即触发功能,用最后一次的触发来实现防抖功能。

取消

取消,优化了用户等待时间过长这样一个问题,但同样也会出现一些小问题,这个问题出现在不是立即执行上面,就上面的input输入框而言,当我在漫长的等待过程中,点击了取消,这个事件就永远不会触发,有可以我们想要的结果是当我们点击了取消,事件直接触发了,多好。

因为上面这个需求,我在underscore上面改动了一点点,达到了我想要的目的。

写出自己underscore防抖

跟紧时代的潮流,我自己写的underscore防抖是以ES6的calss写的,同样也是映着模块化开发的思想。话不多说,先把代码放上。

class Debounce {
    constructor(fn,time = 1000,immediate = false){
         if(typeof fn !== "function"){
             throw new Error("first param is not function");
         }
         this.fn = fn;
         this.time = time;
         this.immediate = immediate;
         this.context;
         this.args;
         this.timeout;
         this.result;
    }
    debounced(){
        let _self = this;
        return function(...args) {
            _self.context = this;
            _self.args = args;
            if (_self.timeout) {
                clearTimeout(_self.timeout);
            }
            if (_self.immediate) {
                let callNow = !_self.timeout;
                _self.timeout = setTimeout(() => {
                    _self.timeout = null;
                },_self.time);
                if(callNow) {
                    _self.result = _self.fn.apply(_self.context, _self.args);
                }
            } else {
                _self.timeout = setTimeout( () => {
                    _self.fn.apply(_self.context,_self.args);
                },_self.time)
            }
            return _self.result;
        } 
    }
    cancel(){
        if(!this.immediate) {
            this.fn.apply(this.context,this.args);
        }
        clearTimeout(this.timeout);
        this.timeout = null;
    }
}

使用方法

var box = document.getElementById("box");
var input = document.getElementById("input");
var span = document.getElementById("span");
function func(e) {
    console.log(e);
    console.log(this);
    box.innerHTML = count++;
    return 123;
};
function test(e) {
    console.log(e);
    span.innerHTML = count++;
}

//在这里在这里
const  inputDebounce = new Debounce(test,10000);
const mouseDebounce = new Debounce(func,10000,true);
input.oninput = inputDebounce.debounced();
box.onmousemove = mouseDebounce.debounced();

class中的constructor方法参数采用参数默认值的形式,把time和immediate写成默认值,并在代码中对第一个参数是不是function做了检测。然后把所有的所要用到的变量值暴露到了实例属性上,underscore防抖是事件函数的this和arguments都在内部。 暴露出来的目的就是为了实现取消功能。在cancel方法中判断如果当前不是立即执行的行为,当触发取消事件的时候就必须立即执行事件函数。

以input为例,代码如下

document.getElementById("button").addEventListener('click', function () {
    inputDebounce.cancel();
})

把等待时间改为10000试验一下。

成功了,此时不是立即执行状态,在漫长的等待中当我点击close按钮的时候,1出现了,开心。