爲何會有去抖和節流這類工具函數?javascript
在用戶和前端頁面的交互過程當中,不少操做的觸發頻率很是高,好比鼠標移動 mousemove 事件, 滾動條滑動 scroll 事件, 輸入框 input 事件, 鍵盤 keyup 事件,瀏覽器窗口 resize 事件。html
在以上事件上綁定回調函數,若是回調函數是一些須要大量計算、消耗內存、HTTP 請求、DOM 操做等,那麼應用的性能和體驗就會很是的差。前端
去抖和節流函數的根據思想就是,減小高頻率事件處理函數 handler 的執行頻率(注意是事件處理函數,不是事件回調函數),將屢次事件的回調合併成一個回調來執行,從而優化性能。java
去抖(debounce),也叫防抖,那抖動指的是什麼呢?抖動意味着操做的不穩定性,你能夠理解成躁動症,安靜不下來~防抖的含義即是爲了防止抖動形成的結果不許確,等到穩定的時候再處理結果。ajax
好比在輸入事件,鼠標移動,滾動條滑動,鍵盤敲擊事件中,等到中止事件觸發,頻率穩定爲零後,纔開始執行回調函數,也就是所謂的沒有抖動後處理。數組
我的總結:去抖,就是事件觸發頻率穩定後,纔開始執行回調函數, 一連串的事件觸發,但只進行一次事件處理。瀏覽器
頻率就是單位時間觸發的次數,若是單位時間內,事件觸發超過一次,就只執行最後一次,若是單位時間內沒有觸發超過一次,那就正常執行。去抖分爲延遲執行和當即執行兩種思路。閉包
看一個簡單版的去抖函數延遲執行實現:app
<div>
輸入框: <input type="text" id="exampleInput">
</div>
<script> window.onload = function() { var inputEl = document.getElementById("exampleInput"); inputEl.oninput = debounce(ajax); // debouce 函數執行了,返回一個函數,該函數爲事件的回調函數 // 事件真正的處理函數(handler),參數是回調函數傳遞過來的。 // 常見場景就是邊輸入查詢關鍵字,邊請求查詢數據,好比百度的首頁搜索 function ajax(event) { console.log("HTTP 異步請求:", event.target.value); // $.ajax() 請求數據 ... } function debounce(func, delay) { // 參數爲傳入的事件處理函數和間隔時間 var interval = delay || 1000; var timer = null; // 閉包保存的 timer 變量,會常駐內存 return function(args) { // 返回的匿名函數是事件的回調函數,在事件觸發時執行,參數爲 DOM 事件對象(event) var context = this; // 事件的回調函數中,this 指向事件的綁定的 DOM 元素對象(HTMLElement) console.log(timer); clearTimeout(timer); // 若是事件回調函數中存在定時器,則清空上次定時器,從新計時。若是間隔時間到後,處理函數天然就被執行了。 timer = setTimeout(function() { func.call(context, args); // 定時器時間到後,執行事件真正的處理函數 handler // 執行的事件處理函數(handler),須要把調用對象 this 和事件對象 傳遞過去,就像沒被debounce處理過同樣 }, interval) } } } </script>
複製代碼
上面代碼中個人註釋已經可以說明整個去抖的過程,再來囉嗦幾句話~dom
以上就是去抖函數的基本思想, 能夠參考示意圖
下面這張圖是高設 3 裏講的節流函數,實際上是這一節所說的去抖函數,高設 3 將 timer 變量用傳入的處理函數的屬性代替了而已。
第二節的簡單版去抖函數能知足大部分只須要觸發一次事件處理的去抖場景:輸入框輸入關鍵字查詢搜索結果。
可是有一個問題,假如我想輸入框輸入內容時,第一個字輸完就請數據怎麼作? 你能夠理解爲,你能夠立刻開始說話,可是說完話後 5 分鐘不能說話,若是 5 分鐘內說話,則接下來再加 5 分鐘不能說話。若是 5 分鐘後沒說話, 那麼接下來,你又能夠先說話,而後閉嘴 5 分鐘~
因此,引出來了當即執行版的去抖函數。
取消功能實現
<div>
輸入框: <input type="text" id="exampleInput">
</div>
<script> window.onload = function() { var inputEl = document.getElementById("exampleInput"); inputEl.oninput = debounce(ajax, 1000, true); // debouce 函數執行了,返回一個函數,該函數爲事件的回調函數 // 事件真正的處理函數(handler),參數是回調函數傳遞過來的。 function ajax(event) { console.log("HTTP 異步請求:", event.target.value); } function debounce(func, delay, immediate) { var interval = delay || 1000; var timer = null; // 定時器的初始值爲 null, 因此第一次觸發事件會當即執行,整個過程當中 timer 充當了 flag 的做用,判斷可否當即執行(第一次或者上一次當即執行後超過了間隔時間) return function(args) { var context = this; // 事件的回調函數中,this 指向事件的綁定的 DOM 元素對象(HTMLElement) console.log(timer); clearTimeout(timer); // 每次有新事件觸發,都會清除以前的定時器,若是能夠當即執行則執行,若是不能夠當即執行則從新建立定時器。 if (immediate) { // 若是上一次的 timer 不爲 null, 說明自上一次事件觸發而且當即執行處理函數後,間隔時間還未結束。因此 timer 本應爲數字 id,不爲 null! callNow = !timer; timer = setTimeout(function() { timer = null; // 每次事件觸發,並在定時器時間超事後, 把定時器變量設置 null, 從而能夠判斷出下一次是否可以當即執行。 }, interval); if (callNow) { func.call(context, args); } } else { timer = setTimeout(function() { func.call(context, args); // 定時器時間到後,執行事件真正的處理函數 handler }, interval) } } } } </script>
複製代碼
上面代碼的註釋,能夠解釋整個流程,下面大體說一下:
看看效果:
假如去抖函數的間隔時間爲 5 秒鐘,我在這 5 秒鐘內又想當即執行能夠怎麼作?因而咱們給回調函數加個取消函數屬性。
函數也是一個對象,能夠像其餘通常對象那樣添加方法:
<div>
輸入框: <input type="text" id="exampleInput"><button id="cancelBtn">取消</button>
</div>
<script> window.onload = function() { var inputEl = document.getElementById("exampleInput"); var debouncedFunc = debounce(ajax, 5000, true); // 將事件處理函數通過去抖函數處理。 inputEl.oninput = debouncedFunc; // 綁定去抖後的事件回調函數 var cancelBtnEL = document.getElementById("cancelBtn"); cancelBtnEL.onclick = debouncedFunc.cancel; // 綁定回調函數的屬性 cancel 方法,點擊頁面,重置去抖效果 function ajax(event) { console.log("HTTP 異步請求:", event.target.value); } function debounce(func, delay, immediate) { var interval = delay || 5000; var timer = null; var revokeFunc = function(args) { var context = this; clearTimeout(timer); if (immediate) { callNow = !timer; timer = setTimeout(function() { timer = null; }, interval); if (callNow) { func.call(context, args); } } else { timer = setTimeout(function() { func.call(context, args); }, interval) } } revokeFunc.cancel = function() { clearTimeout(timer); // 清空上一次事件觸發的定時器 timer = null; // 重置 timer 爲 null, 從而下一次事件觸發就能當即執行。 } return revokeFunc; } } </script>
複製代碼
看看效果:
去抖函數的意義在於合併屢次事件觸發爲一次事件處理,從而下降事件處理函數可能引起的大量重繪重排,http 請求,內存佔用和頁面卡頓。
另外,本文有關 this, call, apply,閉包的知識,能夠翻看我以前分享的文章。
歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。