前端開發工做中,咱們常常在一個事件發生後執行某個操做,好比鼠標移動時打印一些東西:前端
1 window.addEventListener("mousemove", ()=>console.log(123)); 2 //測試發現鼠標移動了1毫米,回調函數執行了將近10次,這種作法是很是耗費資源的。
閉包
解決方法就是每進一我的都從新倒計時N秒再關門,這樣只要每一個人都在前一我的進去N秒以內進門,那麼每進一我的,電梯都會從新計時N秒,因此它只會在最後一我的進門N秒以後再啓動關門程序。app
這裏有三個關鍵點:「事件是高頻率發生的」、「在前一個發生後N秒內發生下一個」、「從新倒計時」。函數
函數防抖(debounce)就是以上解決方案的JavaScript實現,上面代碼的改寫思路是:每次事件發生後都只作兩件事——「清除舊的計時器」、「設置新的計時器」測試
//重置計時器的函數 function debounce(func, ms){ let timer = null; function reTimer(){ //從新計時 clearTimeout(timer) timer = setTimeout(func, ms) } return reTimer; } //要執行的動做 function handle(){ console.log("--- do something ---") } //綁定事件:每次鼠標移動時,就會執行debounce返回的reTimer函數 window.addEventListener("mousemove", debounce(handle, 1000))
前面說了,每次事件發生後都只作兩件事——「清除舊的計時器」、「設置新的計時器」,那麼爲何要在addEventListener裏執行debounce函數呢?this
由於reTimer函數須要操做來自父級做用域的變量timer,而debounce函數就是爲了建立這樣一個做用域,使得每次執行reTimer函數時timer變量都是存在的。編碼
若是要求不使用debounce函數,咱們就得把timer變量定義在addEventListener以前:spa
//重置倒計時 function reTimer(){ if(timer){ clearTimeout(timer) } timer = setTimeout(handle, 1000) } //事件處理 function handle(){ console.log("--- do something ---") } //事件綁定 let timer = null; //或者 window.timer = null window.addEventListener("mousemove", reTimer)
不過,相比使用debounce函數,這樣作就不那麼優雅了。設計
addEventListener的目的是操做timer變量,而timer在debounce的做用域內,addEventListener訪問不到,因此用debounce返回的reTimer去訪問,這就是閉包了。code
防抖是讓重複事件的處理函數只在最後一次發生時執行,而閉包只是一個更好的實現方案。
理解了函數防抖,函數節流也就好辦了,咱們只須要理解場景和方案。
假如咱們正在作一個輸入框,要求每輸入一個字符都調用一個API來查詢數據,從而實現聯想、自動補全等功能,然而咱們的輸入速度是很快的,可能還沒等第一個字符的查詢結果出來,第二個字符就已經敲進去了,因此咱們須要讓查詢頻率小一點,具體作法就是在輸入的過程當中,每隔N秒才查詢一次。
這裏的關鍵點是:「事件是高頻率發生的」、「在前一個發生後N秒內發生下一個」、「一個計時結束後再從新計時」
節流函數(throttle)
/* * 節流函數生成器 * 傳遞事件處理函數和延遲時間 * 返回節流函數 */ function throttleGen(fn, delay) { let timer = null; function throller() { if (timer === null) { timer = setTimeout(function () { fn(); timer = null; }, delay) } } return throller; } //事件處理函數 function handle() { console.log('-- do something --'); } //綁定事件 window.addEventListener("mousemove", throttleGen(handle, 1000))
function debounce(fn, delay) { let timer = null; return function () { var _this = this; //這裏改了 clearTimeout(timer); timer = setTimeout(function () { fn.apply(_this); //這裏改了 }, delay); }; }