背景:我在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 },
若有紕漏,懇請指出,共同進步,謝謝^_^