背景:我在segmentfault提了个问题如何稀释onscroll事件,问题以下:javascript
面试时问到这个问题,是这样的:
面试官问一个关于滚动到某个位置的时候出现一个顶部的导航栏,答完以后,她接着问一滚动onscroll就会执行不少不少次,如何稀释它?为了肯定她说的是“稀释”,我让她重复了遍,我给出的解决方法是,用一个变量,在事件处理的时候让它自增,判断达到必定大小就执行一次实际的事件:javavar i = 0; // 累积变量 window.onscroll = function(){ i++; if(i%500==0){ // 执行实际的事件 } }
- 她并不满意,问最后如何释放这个变量?
- ……
- 接着她说:“我要的是稀释onscroll的执行次数,而不是这个(我所指的实际的事件)的执行次数。”
- 我非常不解,鼠标一滚动就触发这个事件,如何能减小它的执行次数,如何稀释它?难道动态绑定/解绑事件,如何操做?
网上都没有找到相关类型的问题,这个问题算不算变态级的面试题?若是不是,请给出解决方案,先谢谢了。git
第一个回答认为这是函数节流,顿时恍然大悟,纳为答案,这是我和该名词的初次接触,本觉得就这样结束了,没想到接下来又吸引了一大波程序员的眼球。程序员
基本地,指出使用 throttle 和 debounce 两种方式:github
throttle(译:节流阀):就是函数节流的意思,控制函数调用的频度,固定时间间隔执行,即连续的调用中,无论频度如何,只间隔固定时间(或大于该时间,这时频度较低)执行一次,不是问题中的“稀释”。面试
debounce(译:防反跳?):就是去抖的意思,空闲控制,在必定空闲时间间隔内的调用不予实现,一个简单的实现以下:segmentfault
1 var timer = null; 2 document.addEventListener('mousemove', function () { 3 if (timer) { 4 clearTimeout(timer) 5 } 6 timer = setTimeout(function(){ 7 console.log("mousemove"); 8 }, 100); 9 } 10 );
主要应对高度频发的调用,100的意思不是100ms执行一次,而是当调用间隔时间不超过100ms,即鼠标移动速度过快的话,console.log()会一直不被执行,除非移动间隔时间大于100ms,用网友bumfod的话,「函数节流让一个函数只有在你不断触发后停下来歇会才开始执行,中间你操做得太快它直接无视你。」,不是问题中的“稀释”。浏览器
综上,诚然 throttle 和 debounce 都能很好地解决性能问题,两者稀释的是业务逻辑的执行次数,但都不是问题所要求的,这时我就以为这个问题有点牵强了,由于无论怎样,无论有没有显性地定义 scroll 事件,浏览器都会触发 scroll 事件的,差异在于有没有 callback,有没有要执行的东西而已。app
若是非要“减小scroll 的执行次数”,这里有一位和我不谋而合的网友代码,经过setTimeout,执行一次再延时从新绑定事件监听器,这种方法稀释的是回调的执行次数:框架
1 var cb = { 2 onscroll:function() { 3 console.log("scrolling"); 4 window.removeEventListener("scroll", cb.onscroll, false); // 这里移除事件监听器 5 setTimeout(function() { 6 console.log("DONE"); 7 window.addEventListener("scroll", cb.onscroll, false); 8 }, 200); // 200ms后从新绑定事件监听器 9 } 10 }; 11 window.addEventListener("scroll", cb.onscroll, false);
还有同窗引出了阻塞渲染、影响页面UI响应等的问题,
其余:
框架辅助 _debounce(underscore.js 里的 debounce 函数)
1 /** 2 * [debounce description] 3 * @param {[type]} func [回调函数] 4 * @param {[type]} wait [等待时长] 5 * @param {[type]} immediate [是否当即执行] 6 * @return {[type]} [description] 7 */ 8 _.debounce = function(func, wait, immediate) { 9 var timeout, args, context, timestamp, result; 10 11 var later = function() { 12 var last = _.now() - timestamp; 13 14 //小于wait时间,继续延迟wait-last执行later,知道last >= wait才执行func 15 if (last < wait && last > 0) { 16 timeout = setTimeout(later, wait - last); 17 } else { 18 timeout = null; 19 if (!immediate) { 20 result = func.apply(context, args); 21 22 if (!timeout) context = args = null; 23 } 24 } 25 }; 26 27 return function() { 28 context = this; 29 args = arguments; 30 timestamp = _.now(); 31 //是否当即执行 32 var callNow = immediate && !timeout; 33 34 if (!timeout) timeout = setTimeout(later, wait); 35 36 if (callNow) { 37 result = func.apply(context, args); 38 context = args = null; 39 } 40 41 return result; 42 }; 43 };
typeahead 的 throttle 实现源码:
1 throttle: function(func, wait) { 2 var context, args, timeout, result, previous, later; 3 previous = 0; 4 later = function() { 5 previous = new Date(); 6 timeout = null; 7 result = func.apply(context, args); 8 }; 9 return function() { 10 var now = new Date(), 11 remaining = wait - (now - previous); 12 context = this; 13 args = arguments; 14 if (remaining <= 0) { //若是大于间隔时间(wait) 15 clearTimeout(timeout); 16 timeout = null; 17 previous = now; 18 result = func.apply(context, args); 19 } else if (!timeout) { //小于,延时调用later 20 timeout = setTimeout(later, remaining); 21 } 22 return result; 23 }; 24 },
若有纰漏,恳请指出,共同进步,谢谢^_^