「節流」和「防抖」都是用來提升用戶體驗,提升網站性能的手段,它們的技術手段都是「強制事件處理函數在特定的時間段內執行」。這樣解釋可能仍是不夠直觀。舉兩個例子吧:ajax
1: 比方說咱們給document綁定了一個scroll的事件,scroll事件是每滑動一個px,scroll的處理函數就會被調用執行,若是在你的處理函數裏面恰巧作了一個很花時間或者很花空間的事情,比方說複雜的運算啊,ajax請求啊,那這樣頁面就可能出現卡頓的狀況。app
2: 頁面上有個地址的輸入框,你但願根據客戶的輸入內容,去幫客戶補全。假如說這個地址列表須要經過ajax請求來獲取,那咱們必定是但願在客戶中止輸入了以後再去請求ajax而後來補全,而不是客戶一邊輸入就一直請求ajax。函數
針對上面舉例的狀況,其實運用節流和防抖均可以作到,只是它們之間又有必定的區別:性能
防抖:防抖是每次想要執行這個函數,都得先等上一段時間。
節流:節流是在必定的時間段內,函數最多能夠被調用多少次。也能夠理解爲函數以必定的頻率被調用。網站
語言老是蒼白顯得,直接來看代碼的實現吧。咱們先來實現一個防抖:this
//實現防抖函數 function debouncing(fn, waitTime){ let timer = undefined; return function(){ let context = this; let args = arguments; clearTimeout(timer); timer = setTimeout(function(){ fn.apply(context, args); }, waitTime) } } //scroll事件的處理函數 function scrollHandler(event){ console.log(new Date(event.timeStamp)); } //document的scroll事件上使用防抖函數 document.addEventListener('scroll', debouncing(scrollHandler,100), false);
實現防抖函數的核心就是每次事件被觸發的時候,咱們不是當即去調用相應的handler,而是每一次都從新設置一個timeout,等待一段時間,而後再執行咱們的handler.code
2: 如今來嘗試實現一個節流函數:事件
function throttling(fn, intervalTime){ let inInterval = false; return function(){ let context = this; let args = arguments; if(!inInterval) { fn.apply(context, args); inInterval = true; setTimeout(function(){ inInterval = false; }, intervalTime) } } } function scrollHandler(event){ console.log(new Date(event.timeStamp)); } document.addEventListener('scroll', throttling(scrollHandler,500), false);
節流的核心是管理一個布爾值開關變量(inInterval),以必定的頻率切換它的true值和false值,事件處理函數只在這個開關變量值爲某個特意值的時候才執行,以此來實現事件處理函數以必定的頻率被調用。ci
節流函數它的實現有不少種,多種就在於控制這個開關變量的值的條件,會不同。在上面的例子裏,我經過setTimeout的方式,間斷性的來改變inInterval的值。回調函數
如今來詳細分析一下上面的實現:
1: 第一次scroll事件觸發的時候,scrollHandler被當即執行。這個是我我的的一個考慮,但願對於第一次的事件觸發能立刻有一個回饋給客戶。
2: 當第一次回調函數執行完了以後,咱們立刻把'inInterval=true'
, 假如這時候第二次scroll觸發,代碼執行到 if(!inInterval)
,此時條件表達式的值爲false, 因此scrollHandler不會被當即執行。在這以後的第N次scroll事件觸發的時候,inInterval都有可能仍是是true,那麼回調函數會一直不被執行。
3: 咱們以前在把inInterval設置爲true以後,同時設置了一個timeout,在通過必定的時間(intervalTime)以後,inInterval會被設置爲false; 假如在這以後立刻又觸發了一次scroll事件,代碼走到if(!inInterval)
,條件爲true,scrollHandler就能夠被執行了。
這一切看起來是這麼完美地自圓其說,可是上面的代碼存在一個問題,咱們沒有考慮到一個極端狀況:假如咱們最後一次的scroll事件,正好發生在這個循環時間內,那它就永遠得不到執行了。這個可能會是一個隱藏的bug, 比方說你在進行一次拖拽事件,那目標元素可能永遠都拖不到目的地。
因此咱們要改一下代碼,讓最後一次事件的回調函數老是能被執行:
function throttling(fn, intervalTime){ let inInterval = false; let lastTimer = undefined; let timer = undefined; return function(){ let context = this; let args = arguments; if(!inInterval) { clearTimeout(lastTimer); //這行代碼很重要 fn.apply(context, args); inInterval = true; timer = setTimeout(function(){ inInterval = false; }, intervalTime) }else{ clearTimeout(lastTimer); lastTimer = setTimeout(function(){ fn.apply(fn, args); inInterval = false; }, intervalTime); } } } function scrollHandler(event){ console.log(new Date(event.timeStamp)); } document.addEventListener('click', throttling(scrollHandler,1000), false);
上面的代碼,要特別解釋一下這一行代碼:clearTimeout(lastTimer); //這行代碼很重要
假如咱們如今處理一個點擊事件,若是咱們不加這行代碼的話,會出現先點擊的click事件反然後執行的問題。好比;咱們的intervalTime設置爲10s, 而後咱們分別在第0s, 第5秒,第12秒都進行一次點擊,咱們經過console.log(new Date(event.timeStamp))
打印每一次事件發生時的時間, 咱們會看到第5秒的那個click事件會比第12秒的那個click後輸出,這就說明這裏有問題。
因此咱們要在if(!inInterval){}裏面把lastTimer給清掉,也就是經過clearTimeout(lastTimer);
這行代碼。