函数节流和函数防抖
这段时间去面试了两家当前比较拽的互联网公司,回来一总结,广度略有,深度堪忧。这么看来可能觉得还是好事,最近没事在掘金上看看,不知不觉的关注的标签已经有40来个了。
eslint,requirejs,three,Web Components,函数式编程,Visual Studio Code,JSON,TypeScript,vuex,WebAssembly,HTTPS,WebGL,DOM,Canvas,敏捷开发,MVVM,React Native,响应式设计,HTTP,全栈,稀土,电子书,chrome,微信,代码规范,CSS,正则表达式,Node.js, 前端框架, HTML,设计模式,面试,程序员,算法,架构,Webpack......
bla bla bla......
前端就是苦,前端就是累。代码大家都能写,写出什么样的代码,那就是功力。
闲话说的太多了,前端时间自己用vue写了个相当简单的web音乐播放器,写完毕以后一直有几个问题困扰我。
1.vue如何高可用的组件开发。
2.自己写的搜索(autoComplete),返回数据不准确,可能返回以前关键字查询的结果。
3. vuex 如何在错误的时候不阻止程序的继续执行。
这里我说的是第二个问题,
我原来的考虑,搜索的时候传递关键字过去, 返回的时候,除了被搜索到的数据,还包括关键字本身。
如果当前的输入框的值和返回的数据的关键字匹配,那么展现,反之丢弃。
这里也有问题,如果关键字前后两次相同,那就会数据填充两次。比如关键字为 a->b->a,第一个a落后于b返回。但至少保证了展现的数据是自己期望的数据。
后来看了一下
百度的搜索,也是每次都发送请求,但是一些前面发送的请求会被取消掉,返回html和脚本
bing的搜索,也是每次都发送请求,没有取消,返回数据,html和脚本。
搜狗,每次发送请求,没有取消,返回的是数据+脚本。
但是都一个特点,就是返回的都足够快,这就有点尴尬了。
回头再看看自己搜索的问题,才发现问题的是自己的逻辑处理问题
1.当字符为空的时候没有发请求但是没有清空数据
2.oninput,focus处理有问题
晕死,一天在掘金看到函数节流和函数防抖,本事想应用到这个及时搜索上面来,可是啊哈,走歪了。
回归,我们来说说函数节流和函数防抖。
函数节流 & 函数防抖
Throttling enforces a maximum number of times a function can be called over time. As in "execute this function at most once every 100 milliseconds".
Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in "execute this function only if 100 milliseconds have passed without it being called".
函数节流:是确保函数特定的时间内至多执行一次。
函数防抖:是函数在特定的时间内不被再调用后执行。
函数节流underscore的实现(解释,借用的是JS魔法堂:函数节流(throttle)与函数去抖(debounce))
.throttle = function(func, wait, options) { /* options的默认值 * 表示首次调用返回值方法时,会马上调用func;否则仅会记录当前时刻,当第二次调用的时间间隔超过wait时,才调用func。 * options.leading = true; * 表示当调用方法时,未到达wait指定的时间间隔,则启动计时器延迟调用func函数,若后续在既未达到wait指定的时间间隔和func函数又未被调用的情况下调用返回值方法,则被调用请求将被丢弃。 * options.trailing = true; * 注意:当options.trailing = false时,效果与上面的简单实现效果相同 */ var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = _.now(); if (!previous && options.leading === false) previous = now; // 计算剩余时间 var remaining = wait - (now - previous); context = this; args = arguments; // 当到达wait指定的时间间隔,则调用func函数 // 精彩之处:按理来说remaining <= 0已经足够证明已经到达wait的时间间隔,但这里还考虑到假如客户端修改了系统时间则马上执行func函数。 if (remaining <= 0 || remaining > wait) { // 由于setTimeout存在最小时间精度问题,因此会存在到达wait的时间间隔,但之前设置的setTimeout操作还没被执行,因此为保险起见,这里先清理setTimeout操作 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // options.trailing=true时,延时执行func函数 timeout = setTimeout(later, remaining); } return result; }; };
略有复杂,我们当然有简单版本 ,基本的思想就是利用闭包生成一个新的执行函数,闭包里面记录上一次的调用时间,再次调用时时间差和允许的调用周期比较。
function throttle(method,delay,duration){ var timer=null, begin=new Date(); return function(){ var context=this, args=arguments, current=new Date();; clearTimeout(timer); if(current-begin>=duration){ method.apply(context,args); begin=current; }else{ timer=setTimeout(function(){ method.apply(context,args); },delay); } } }
函数防抖在underscore的实现,其基本思路,就是内部计时,达到指定时间,就执行,不然启用延时。
_.debounce = function(func, wait, immediate) { // immediate默认为false var timeout, args, context, timestamp, result; var later = function() { // 当wait指定的时间间隔期间多次调用_.debounce返回的函数,则会不断更新timestamp的值,导致last < wait && last >= 0一直为true,从而不断启动新的计时器延时执行func 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(); // 第一次调用该方法时,且immediate为true,则调用func函数 var callNow = immediate && !timeout; // 在wait指定的时间间隔内首次调用该方法,则启动计时器定时调用func函数 if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; };
那么应用场景:
函数节流(throttle)
1. 频繁的mousemove/keydown,比如高频的鼠标移动,游戏射击类的
2. 搜索联想(keyup)
3. 进度条(我们可能不需要高频的更新进度)
4. 拖拽的dragover等
5. 高频的点击,抽奖等(哈哈,邪恶)
函数防抖(debounce)
1. scroll/resize事件
2. 文本连续输入,ajax验证/关键字搜索
参考:
浅谈 Underscore.js 中 _.throttle 和 _.debounce 的差异
JS魔法堂:函数节流(throttle)与函数去抖(debounce)