首先:
先谈谈类似上图的需求,有可能需要实现用户在输入内容的时候,使用ajax实时传输内容到服务器。
我们肯定不希望看到,每次输入都请求一次,如此频繁的请求,恐怕对身体也不是很好吧?
其次:
解决的思路之一:underscore.js类库中的_.debounce方法。关于什么是underscore,什么是函数式编程等等,我觉得有必要了解下。
说正事:_.debounce方法的作用是防抖动,这就是传说中的函抖术,当你的事件在不断触发的时候,会根据你设置的间隔时间只触发一次回调
像这样:
<body> <input type="text" id="chenjian" style="200px;height:50px"></input> </body> <script type="text/javascript" src="underscore.js"></script> <script> var chenjian = document.getElementById('chenjian'); function jj (){ console.log(this.value); } chenjian.addEventListener('keyup',_.debounce(jj,500)); </script>
当我不断触发键盘事件,如果我触发事件的时间间隔低于500毫秒,则不会调用jj函数,这样限制了频率,就不会对身体不好啦。
用起来倒是很方便,相对应的还有 _.throttle 方法。
接下来我们看看 debounce 的源码吧
_.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { var last = _.now() - timestamp; if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; };
下面是我自己有注释的重写,省略了第三个参数 immediate ,不过对理解问题不大。
/* fnc 是传入的函数
* wait 是设置的时间间隔
*/
function debounce(fnc,wait){
//time1 实时更新的事件触发时间
//context 存放this
//args 存放参数
//time2 最近一次触发事件离最后一个定时器执行之间 间隔的时长
//result 存放回调函数的结果
// timer 定时器 var time1, context, args, time2, result, timer;
function cj(){ //触发事件后,过了设置的时间间隔wait,然后进入cj // console.log('调用了cj函数') var time2 = _.now() - time1;//调用cj函数时,离你最近一次触发事件过了time2的时间 console.log('调用cj函数时,离你最近一次触发事件过了time2的时间:' + ( time2)); if(time2 < wait && time2 > 0 ) { //如果时间差小于设置的间隔那就说明还没到时间去调用fnc //重新注册一个定时器,到规定的时间去调用func,这里有个问题,为什么不直接去调用fnc,而是调用cj本身 //调用fnc的后果就是到了时间调用fnc后,之后就不会调用了,因为进程里面只注册了一个定时器 //如果换成cj,那么其实一直在注册定时器的,实际上就是:我希望在监听到键盘事件后的2s之后调用回调函数,在这2s之内如果 //事件又被触发了,不执行回调,因为这样就避免了一监听到就调用带来的损耗 //实现的原理就像源码注释中:难点就是如何在正确的时间调用回调 //你第一次触发事件的时候,我记下时间并且注册一个定时器(实际上每次触发事件,都会更新time1),然后在你设置的间隔时间之后去调用回调。 // 但是在你设置的时间间隔这段时间内,你有可能会继续触发事件(注意,这时你原先注册的定时器还是会正常运行的),你不断触发事件 //需要不断对你触发事件的时间进行判断,以此来修正下次回调执行的时间 //判断最近一次的 // 事件触发距离这个定时器 // 是过了多久,如果没有超过设置的 // 500ms,那就会在比如还剩300ms的地方注册一个定时器 // 这个定时器就会正确的在500ms后执行, //其实就是在不断修正fnc调用的时间。 timer = setTimeout(cj,wait - time2); } else { //说明是时候调用func了,已经过了或者刚好到了设置的间隔 timer = null;//定时器置空一下 result = fnc.apply(context, args);//绑定this指向 if (!timer){ context = args = null; } } } return function(){ //每次触发事件都会执行 console.log('触发了事件'); time1 = _.now();//现在的时间 context = this;//是指调用这个方法的上下文 args = arguments;//方法传入的参数,即debounce函数的第一个函数的参数 if(!timer){//如果不存在定时器,就注册一个定时器,在设置的时间间隔后调用cj timer = setTimeout(cj,wait); } //如果存在了定时器说明还没到设置的时间间隔,就又触发了一次事件,此时要做的处理 //就是不做任何操作 return result; } }
看得有点晕了。。。一句话总结:
最后一次执行的定时器 根据 最后一次触发事件的时间 距离自己的时间间隔来判断,是继续设定一个定时器来满足延迟500ms后执行,还是立刻执行。
打个比方,我两约定:原本不动的我会在你停止不动2s后开始跑步。我的任务就是只要你不动的时候我就开始计时,只要过了2s,我就可以跑路了。但你有可能停下来,只过了1s你就又开始动起来了。我肯定不能跑,因为这是按照约定。 在这里约定中的我开始跑步就是 函数中的 fnc 函数 ,2s就是wait ,我的任务就是定时器的任务,就是 JJ 函数。你不动就是指触发了事件。
其中个人觉得的理解难点就是,如何去实现确保你不动2s后我开始跑步,源码中就是通过你不动的那个时间点来修正我下一次开始跑步的时间点。
额。。。大概意思就是这样。有啥问题还请有缘人指点。