javascript中的函数节流和函数去抖
带着问题去尝试
首先我们要知道为什么要用到函数节流和函数去抖?我们带着以下的疑问来进行分析!
1.比如搜索框,你会用到什么事件(change、blur、keyup等)?去做什么效果?
2.再比如scroll滚动事件,怎么去触发?是滚一段距离触发一次?还是滚一圈触发一次?还是滚一次触发一次?
3.还包括mouseover事件是怎么触发呢?
......
场景实例
函数节流和去抖的出现场景,一般都伴随着客户端 DOM 的事件监听。举个例子,实现一个原生的拖拽功能(不能用 H5 Drag&Drop API),需要一路监听 mousemove 事件,在回调中获取元素当前位置,然后重置 dom 的位置(样式改变)。如果我们不加以控制,每移动一定像素而触发的回调数量是会非常惊人的,回调中又伴随着 DOM 操作,继而引发浏览器的重排与重绘,性能差的浏览器可能就会直接假死,这样的用户体验是非常糟糕的。我们需要做的是降低触发回调的频率,比如让它 500ms 触发一次,或者 200ms,甚至 100ms,这个阈值不能太大,太大了拖拽就会失真,也不能太小,太小了低版本浏览器可能就会假死,这样的解决方案就是函数节流,英文名字叫「throttle」。函数节流的核心是,让一个函数不要执行得太频繁,减少一些过快的调用来节流。
说完函数节流,再看它的好基友函数去抖(debounce)。思考这样一个场景,对于浏览器窗口,每做一次 resize 操作,发送一个请求,很显然,我们需要监听 resize 事件,但是和 mousemove 一样,每缩小(或者放大)一次浏览器,实际上会触发 N 多次的 resize 事件,用节流?节流只能保证定时触发,我们一次就好,这就要用去抖。简单的说,函数去抖就是对于一定时间段的连续的函数调用,只让其执行一次。
实例了解
下面就让我们来了解throttle(节流) 和 debounce(去抖)
1.throttle节流,scroll和mouseover亦或者其他方式,每次移动都会触发代码效果,但是这些效果太密集了,占用了系统资源,计算机的计算能力是有限的,我们写的代码应该尽可能的优雅。
2.debounce去抖,本意是从按键去抖过来的,让我们的用户正确的操作之后才去执行代码,且只会执行一次,比如当我们做文本输入的时候,如果用keyup我们不是去节流,而是去判断用户停止了输入。
3.节流保证在一定时间内,只能触发一次。如果我们只要触发一次,这就是去抖。代码上来看差距不是很大。
直接试一下:
//先来个有问题的代码 window.onscroll=function(){ console.log(new Date()); }
控制台会打印:
VM109:2 Sun Mar 12 2017 18:05:50 GMT+0800 (中国标准时间) VM109:2 Sun Mar 12 2017 18:05:50 GMT+0800 (中国标准时间) VM109:2 Sun Mar 12 2017 18:05:50 GMT+0800 (中国标准时间) VM109:2 Sun Mar 12 2017 18:05:50 GMT+0800 (中国标准时间) VM109:2 Sun Mar 12 2017 18:05:50 GMT+0800 (中国标准时间) VM109:2 Sun Mar 12 2017 18:05:50 GMT+0800 (中国标准时间)
...
控制台会一直这样输入,那问题来了,这获取并不是我们想要的,我们并不想让他一直触发下去。
我们要解决这些问题,所以直接看代码:
//我们先来个节流,每秒输出一次 window.onscroll=function(){ if(window.onScrollTag == undefined){ window.onScrollTag=true; console.log(new Date()); setTimeout(function(){ window.onScrollTag=undefined; },1000); } }//先触发 window.onscroll=function(){ if(window.onScrollTag == undefined){ window.onScrollTag=setTimeout(function(){ window.onScrollTag=undefined; console.log(new Date()); },1000); } }//后触发 //从上面的代码可以看出来,我们在一个事件执行的时候就把标记改成不可用。一秒后才可以再次触发操作。 //我们在尝试一下去抖 window.onscroll=function(){ if(window.onScrollTag != undefined){ clearTimeout(window.onScrollTag); } window.onScrollTag=setTimeout(function(){ console.log(new Date()); },1000); }//消抖,消除抖动,感觉这个更好听
//节流 funtion throttle(func, wait) { var lastTime = 0, timeout; return funtion() { var context = this, args = arguments, delay = wait || 100, now = new Date().getTime(); if (now - lastTime <= delay) { return; } lastTime = now; clearTimeout(timeout); timeout = setTimeout(function() { timeout = null; func.apply(context, args); }, delay) } } // 去抖 function debounce(func, wait) { var timeout; return function() { var context = this, args = arguments, delay = wait || 250; clearTimeout(timeout); timeout = setTimeout(function() { timeout = null; func.apply(context, args); }, delay); }; }
throttle 应用场景
函数节流有哪些应用场景?哪些时候我们需要间隔一定时间触发回调来控制函数调用频率?
- DOM 元素的拖拽功能实现(mousemove)
- 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
- 计算鼠标移动的距离(mousemove)
- Canvas 模拟画板功能(mousemove)
- 搜索联想(keyup)
- 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次
debounce 应用场景
函数去抖有哪些应用场景?哪些时候对于连续的事件响应我们只需要执行一次回调?
- 每次 resize/scroll 触发统计事件
- 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)
小结
函数节流和函数去抖的核心其实就是限制某一个方法被频繁触发,而一个方法之所以会被频繁触发,大多数情况下是因为 DOM 事件的监听回调,而这也是函数节流以及去抖多数情况下的应用场景
如果大家感兴趣,可以参考下:http://underscorejs.org/