對防抖和節流的一些理解,作一次記錄。(以前項目中的需求是在輸入框中輸入內容以後,調接口返回值,而後不知道還有節流這波操做,而後就寫了判斷當鼠標失去焦點的時候調接口,後來大佬說可使用節流來實現)前端
防抖和節流算起來應該屬於性能優化的知識,可是處理不當或者是聽任無論就容易引發瀏覽器卡死。就是在綁定scroll、resize這類事件時,當他發生時,被觸發的頻率很是高,間隔很近。若是事件中涉及到大量的位置計算、DOM操做、元素重繪等工做且這些工做沒法在下一個scroll事件觸發前完成,就會形成瀏覽器調幀。加之用戶鼠標滾動每每時連續的,就會持續觸發scroll事件致使調幀擴大、瀏覽器CPU使用率增長、用戶體驗受到影響。尤爲時在涉及與後端的交互中,前端依賴於某中事件如resize、scroll,發送http請求,在這個過程當中,若是不作防抖處理,那麼在事件觸發的一瞬間,就會有不少個請求發過去,增長了服務端的壓力。ajax
chrome
這個按鈕只會在滾動到距離頂部必定位置的時候纔會出現,那麼如今抽象出這個功能需求 --- 監聽滾動條事件,返回當前滾條和頂部的距離。後端
這個需求很簡單,直接寫:瀏覽器
1 function showTop () { 2 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 3 console.log('滾動條位置:' + scrollTop); 4 } 5 window.onscroll = showTop
可是:在運行的時候會發現:這個函數的默認執行頻率過高了!以chrome爲例,咱們能夠點擊選中一個頁面的滾動條,而後點擊一次鍵盤的【向下方向鍵】,會發現函數執行了8-9次!性能優化
然而實際上並不須要如此高頻的反饋,畢竟瀏覽器的性能是有限的,不該該浪費在這裏,因此須要優化這種場景。閉包
app
dom
若是在300ms內再次觸發滾動事件,那麼當前的即便取消,從新開始計時。ide
效果就是:若是在短期內大量觸發贊成事件,只會執行一次函數。
實現:既然前面都提到了計時,那實現的關鍵就在於setTimeOut
這個函數,因爲還須要一個變量來保存計時,考慮維護全局純淨,能夠藉助閉包來實現:
1 /** 2 * fn[function] 須要防抖的函數 3 * delay[number] 毫秒,防抖期限值 4 */ 5 function debounce(fn,delay){ 6 let timer = null; 7 return function(){ 8 if(timer){ 9 //進入該分支語句,說明當前正在一個計時過程當中,而且又觸發了相同事件。因此要取消當前的計時,從新開始計時 10 clearTimeout(timer) 11 timer = setTimeOut(fn,delay) 12 }else{ 13 // 進入該分支說明當前並無在計時,那麼就開始一個計時 14 timer = setTimeOut(fn,delay) 15 } 16 } 17 }
1 function debounce(fn,delay){ 2 let timer = null //藉助閉包 3 return function() { 4 if(timer){ 5 clearTimeout(timer) 6 } 7 timer = setTimeout(fn,delay) // 簡化寫法 8 } 9 } 10 // 而後是舊代碼 11 function showTop () { 12 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 13 console.log('滾動條位置:' + scrollTop); 14 } 15 window.onscroll = debounce(showTop,1000) // 爲了方便觀察效果咱們取個大點的間斷值,實際使用根據須要來配置
防抖也就實現了:定義即:
對於短期內連續觸發的事件(上面的滾動事件),
若是在限定時間段內,不斷觸發滾動事件(好比某個用戶閒着無聊,按住滾動不斷的拖來拖去),只要不中止觸發,理論上就永遠不會輸出當前距離頂部的距離。
其實很簡單:咱們能夠設計一種相似控制閥門同樣按期開放的函數,也就是讓函數執行一次後,在某個時間段內暫時失效,過了這段時間後再從新激活(相似於技能冷卻時間)。
效果:若是短期內大量觸發同一事件,那麼在函數執行一次以後,該函數在指定的時間期限內再也不工做,直至過了這段時間才從新生效。
定時器方案
1 function throttle(fn,delay){ 2 let valid = true; 3 return function(){ 4 if(!valid){ 5 return false; 6 } 7 //執行函數而且在間隔期間內把狀態位設爲無效 8 valid = false; 9 setTimeout(()=>{ 10 fn() 11 valid = true; 12 },delay) 13 } 14 } 15 /* 請注意,節流函數並不止上面這種實現方案, 16 例如能夠徹底不借助setTimeout,能夠把狀態位換成時間戳,而後利用時間戳差值是否大於指定間隔時間來作斷定。 17 也能夠直接將setTimeout的返回的標記當作判斷條件-判斷當前定時器是否存在,若是存在表示還在冷卻,而且在執行fn以後消除定時器表示激活,原理都同樣 18 */ 19 // 如下照舊 20 function showTop () { 21 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; 22 console.log('滾動條位置:' + scrollTop); 23 } 24 window.onscroll = throttle(showTop,1000)
運行以上代碼的結果:
若是一直拖着滾動條進行滾動,那麼會以1s的時間間隔,持續輸出當前位置和頂部的距離。
時間戳方案
1 var throttle = function(fn,delay){ 2 var prev = Date.now(); 3 return function(){ 4 var context = this; 5 var args = arguments; 6 var now = Date.now(); 7 if(now -prev >=delay){ 8 fn.apply(context,args) 9 prev = Date.now(); 10 } 11 } 12 } 13 function handle(){ 14 console.log(Math.random()); 15 } 16 window.addEventListener('scroll',throttle(handle,1000));
時間戳+定時器
1 var throttle = function(func, delay) { 2 var timer = null; 3 var startTime = Date.now(); 4 return function() { 5 var curTime = Date.now(); 6 var remaining = delay - (curTime - startTime); 7 var context = this; 8 var args = arguments; 9 clearTimeout(timer); 10 if (remaining <= 0) { 11 func.apply(context, args); 12 startTime = Date.now(); 13 } else { 14 timer = setTimeout(func, remaining); 15 } 16 } 17 } 18 function handle() { 19 console.log(Math.random()); 20 } 21 window.addEventListener('scroll', throttle(handle, 1000));
搜索框input事件,例如要支持輸入實時搜索可使用節流方案(間隔一段時間就必須查詢相關內容),或者實現輸入間隔大於某個值(如500ms),就當作用戶輸入完成,而後開始搜索,具體使用哪一種方案要看業務需求。
頁面resize事件,常見於須要作頁面適配的時候。須要根據最終呈現的頁面狀況進行dom渲染(這種情形通常是使用防抖,由於只須要判斷最後一次的變化狀況)
函數節流:使得必定時間內只觸發一次函數,原理是經過判斷是否到達必定時間來觸發函數。
區別:函數節流無論事件觸發多頻繁,都會保證在規定的時間內必定會執行一次真正的事件處理函數,而函數防抖只是在最後一次事件後才觸發一次函數。好比在頁面的無限加載場景下,須要用戶在滾動頁面時,每隔一段時間發一次ajax請求,而不是在啊用戶停下滾動頁面操做時纔去請求數據。這種場景就適合用節流技術來實現。