上一節咱們詳細聊了聊高階函數之柯里化,經過介紹其定義和三種柯里化應用,並在最後實現了一個通用的 currying 函數。這一小節會繼續以前的篇幅聊聊函數節流 throttle,給出這種高階函數的定義、實現原理以及在 underscore 中的實現,歡迎你們拍磚。前端
有什麼想法或者意見均可以在評論區留言,下圖是本文的思惟導圖,高清思惟導圖和更多文章請看個人 Github。git
函數節流指的是某個函數在必定時間間隔內(例如 3 秒)只執行一次,在這 3 秒內 無視後來產生的函數調用請求,也不會延長時間間隔。3 秒間隔結束後第一次遇到新的函數調用會觸發執行,而後在這新的 3 秒內依舊無視後來產生的函數調用請求,以此類推。github
舉一個小例子,不知道你們小時候有沒有養太小金魚啥的,養金魚確定少不了接水,剛開始接水時管道中水流很大,水到半滿時開始擰緊水龍頭,減小水流的速度變成 3 秒一滴,經過滴水給小金魚增長氧氣。面試
此時「管道中的水」就是咱們頻繁操做事件而不斷涌入的回調任務,它須要接受「水龍頭」安排;「水龍頭」就是節流閥,控制水的流速,過濾無效的回調任務;「滴水」就是每隔一段時間執行一次函數,「3 秒」就是間隔時間,它是「水龍頭」決定「滴水」的依據。性能優化
若是你還沒法理解,看下面這張圖就清晰多了,另外點擊 這個頁面 查看節流和防抖的可視化比較。其中 Regular 是不作任何處理的狀況,throttle 是函數節流以後的結果,debounce 是函數防抖以後的結果(下一小節介紹)。閉包
函數節流很是適用於函數被頻繁調用的場景,例如:window.onresize() 事件、mousemove 事件、上傳進度等狀況。使用 throttle API 很簡單,那應該如何實現 throttle 這個函數呢?app
實現方案有如下兩種less
這裏咱們採用第一種方案來實現,經過閉包保存一個 previous 變量,每次觸發 throttle 函數時判斷當前時間和 previous 的時間差,若是這段時間差小於等待時間,那就忽略本次事件觸發。若是大於等待時間就把 previous 設置爲當前時間並執行函數 fn。前端性能
咱們來一步步實現,首先實現用閉包保存 previous 變量。函數
const throttle = (fn, wait) => { // 上一次執行該函數的時間 let previous = 0 return function(...args) { console.log(previous) ... } }
執行 throttle 函數後會返回一個新的 function,咱們命名爲 betterFn。
const betterFn = function(...args) { console.log(previous) ... }
betterFn 函數中能夠獲取到 previous 變量值也能夠修改,在回調監聽或事件觸發時就會執行 betterFn,即 betterFn()
,因此在這個新函數內判斷當前時間和 previous 的時間差便可。
const betterFn = function(...args) { let now = +new Date(); if (now - previous > wait) { previous = now // 執行 fn 函數 fn.apply(this, args) } }
結合上面兩段代碼就實現了節流函數,因此完整的實現以下。
// fn 是須要執行的函數 // wait 是時間間隔 const throttle = (fn, wait = 50) => { // 上一次執行 fn 的時間 let previous = 0 // 將 throttle 處理結果看成函數返回 return function(...args) { // 獲取當前時間,轉換成時間戳,單位毫秒 let now = +new Date() // 將當前時間和上一次執行函數的時間進行對比 // 大於等待時間就把 previous 設置爲當前時間並執行函數 fn if (now - previous > wait) { previous = now fn.apply(this, args) } } } // DEMO // 執行 throttle 函數返回新函數 const betterFn = throttle(() => console.log('fn 函數執行了'), 1000) // 每 10 秒執行一次 betterFn 函數,可是隻有時間差大於 1000 時纔會執行 fn setInterval(betterFn, 10)
上述代碼實現了一個簡單的節流函數,不過 underscore 實現了更高級的功能,即新增了兩個功能
配置 { leading: false } 時,事件剛開始的那次回調不執行;配置 { trailing: false } 時,事件結束後的那次回調不執行,不過須要注意的是,這二者不能同時配置。
因此在 underscore 中的節流函數有 3 種調用方式,默認的(有頭有尾),設置 { leading: false } 的,以及設置 { trailing: false } 的。上面說過實現 throttle 的方案有 2 種,一種是經過時間戳判斷,另外一種是經過定時器建立和銷燬來控制。
第一種方案實現這 3 種調用方式存在一個問題,即事件中止觸發時沒法響應回調,因此 { trailing: true } 時沒法生效。
第二種方案來實現也存在一個問題,由於定時器是延遲執行的,因此事件中止觸發時必然會響應回調,因此 { trailing: false } 時沒法生效。
underscore 採用的方案是兩種方案搭配使用來實現這個功能。
const throttle = function(func, wait, options) { var timeout, context, args, result; // 上一次執行回調的時間戳 var previous = 0; // 無傳入參數時,初始化 options 爲空對象 if (!options) options = {}; var later = function() { // 當設置 { leading: false } 時 // 每次觸發回調函數後設置 previous 爲 0 // 否則爲當前時間 previous = options.leading === false ? 0 : _.now(); // 防止內存泄漏,置爲 null 便於後面根據 !timeout 設置新的 timeout timeout = null; // 執行函數 result = func.apply(context, args); if (!timeout) context = args = null; }; // 每次觸發事件回調都執行這個函數 // 函數內判斷是否執行 func // func 纔是咱們業務層代碼想要執行的函數 var throttled = function() { // 記錄當前時間 var now = _.now(); // 第一次執行時(此時 previous 爲 0,以後爲上一次時間戳) // 而且設置了 { leading: false }(表示第一次回調不執行) // 此時設置 previous 爲當前值,表示剛執行過,本次就不執行了 if (!previous && options.leading === false) previous = now; // 距離下次觸發 func 還須要等待的時間 var remaining = wait - (now - previous); context = this; args = arguments; // 要麼是到了間隔時間了,隨即觸發方法(remaining <= 0) // 要麼是沒有傳入 {leading: false},且第一次觸發回調,即當即觸發 // 此時 previous 爲 0,wait - (now - previous) 也知足 <= 0 // 以後便會把 previous 值迅速置爲 now if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); // clearTimeout(timeout) 並不會把 timeout 設爲 null // 手動設置,便於後續判斷 timeout = null; } // 設置 previous 爲當前時間 previous = now; // 執行 func 函數 result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 最後一次須要觸發的狀況 // 若是已經存在一個定時器,則不會進入該 if 分支 // 若是 {trailing: false},即最後一次不須要觸發了,也不會進入這個分支 // 間隔 remaining milliseconds 後觸發 later 方法 timeout = setTimeout(later, remaining); } return result; }; // 手動取消 throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; // 執行 _.throttle 返回 throttled 函數 return throttled; };
節流能夠理解爲養金魚時擰緊水龍頭放水,3 秒一滴
節流實現方案有 2 種
underscore.js
若是你以爲這篇內容對你挺有啓發,我想邀請你幫我三個小忙: