支持上下文context的debounce

虽然debounce已经被大家玩腻了,但是当我们在一个类里面用的时候,总是还会遇到一些麻烦,比如一个页面有三个来自同一个类的实例化对象,每个对象中对自己的一些操作需要用到debounce,是不是意味着必须将this传入到debounce,从而让每个debounce从属于这个对象?于是我对debounce函数进行了改造,使得它可以不再获得一个函数来执行,而是直接执行函数:

/** @desc ensure a given task doesn't fire so often that it bricks browser performance @param object context: this @param string|function factory: function to run, if you pass a string, it will find context[factory] @param number wait: time to wait, ms @param boolean immediate: whether to run factory right now @usage1: `Util.debounce(this, this.request, 200, true)`, run this.request immediately and never run again in 200ms. use first call in 200ms @usage2: `Util.debounce(this, 'request', 200)`, run this.request after 200ms,time clock will be reset if you call again. use last call in last 200ms @usage3: `var factory = this.alert.bind(this, 'word');Util.debounce(this, factory, 200)`, use bind to pass parameters, if you bind, this will not be change by debounce notice:    1.you can use this in factory function and do not need to use `this.request.bind(this)`, factory will bind context automaticly;    2.you must pass factory function name, anonymous function is not allow;    3.no parameters for factory function, unless you use bind and pass function name;    4.context in arrow function will not change; */function debounce(context, factory, wait, immediate) {    if(typeof factory === 'string') {        factory = context[factory];    }

    if(typeof factory !== 'function') {        return;    }

    var queue = context._$$debounce = context._$$debounce || {};    var timer = queue[factory];    var isCallNow = immediate && !timer;    var call = function(factory) {        // original function        if(typeof factory.prototype === 'object') {            factory.call(context);            return;        }        // bound function or arrow function        factory();    };    var delay = function() {        queue[factory] = null;        if(!immediate) {            call(factory);        }    };

    clearTimeout(timer);    queue[factory] = setTimeout(delay, wait);

    if(isCallNow) {        call(factory);    }}

使用方法很简单,主要是在类的原型链方法中去使用。

class Cat {  walk() {}  say() {    debounce(this, this.walk, 200, true)  }}

这样就可以使得其实例化对象拥有这个debounce,当实例化对象调用say方法时,会立即执行自身的walk方法,但是在接下来的200ms内,不会再执行第二次。

这个用法也简化了underscore中debounce的用法:

// underscore中var factory = debounce(function() {}, 200, true)factory()

必须先执行一次debounce,得到它的返回值,这个函数才是反复执行时可以被防抖的函数。而采用本文的方法,则可以不再依靠这个第三方变量factory,而是直接执行debounce函数即可。

究其原理,就是在debounce中利用第一个参数context记录了要执行的factory是否是正处于queue中的函数,如果是,就不会被再次执行。这也为什么要求factory不是匿名函数的原因,因为如果是匿名函数,每一个factory都是不一样的。如果想使用匿名函数,应该考虑使用underscore的debounce,本文恰恰是为了解决在同一个实例化对象的不同方法中使用debounce的问题。情景不同,解决方法也就自然不一样了。

2017-04-13 18:35:20