摘要:多次連續(xù)事件觸發(fā)動作最后一次觸發(fā)之后的指定時間間隔執(zhí)行回調(diào)函數(shù)預(yù)先設(shè)定一個執(zhí)行周期,當(dāng)調(diào)用動作的時刻大于等于執(zhí)行周期則執(zhí)行該動作,然后進(jìn)入下一個新的時間周期。
定義
為了避免某個事件在較短的時間段內(nèi)(稱為 T)內(nèi)連續(xù)觸發(fā)從而引起的其對應(yīng)的事件處理函數(shù)不必要的連續(xù)執(zhí)行的一種事件處理機(jī)制(高頻觸發(fā)事件解決方案)
debounce:當(dāng)調(diào)用動作觸發(fā)一段時間后,才會執(zhí)行該動作,若在這段時間間隔內(nèi)又調(diào)用此動作則將重新計算時間間隔。(多次連續(xù)事件觸發(fā)動作/最后一次觸發(fā)之后的指定時間間隔執(zhí)行回調(diào)函數(shù))
throttle:預(yù)先設(shè)定一個執(zhí)行周期,當(dāng)調(diào)用動作的時刻大于等于執(zhí)行周期則執(zhí)行該動作,然后進(jìn)入下一個新的時間周期。(每個指定時間執(zhí)行一次回調(diào)函數(shù),可以指定時間間隔之前調(diào)用)
1、throttle 保證了在每個 T 內(nèi)至少執(zhí)行一次,而 debounce 沒有這樣的保證
2、每次事件觸發(fā)時參考的時間點,對于debounce來是上一次事件觸發(fā)的時間并且在延時沒有結(jié)束時會重置延時;
throttle 是上一次 handler 執(zhí)行的時間并且在延時尚未結(jié)束時不會重置延時
響應(yīng)速度跟不上觸發(fā)頻率,往往會出現(xiàn)延遲,導(dǎo)致假死或者卡頓感
實現(xiàn) 去抖 debounce空閑控制:所有操作最后一次性執(zhí)行
【簡潔版】
/** * @param fn {Function} 實際要執(zhí)行的函數(shù) * @param delay {Number} 延遲時間,也就是閾值,單位是毫秒(ms) * @return {Function} 返回一個“去彈跳”了的函數(shù) */ function debounce(fn, delay) { // 定時器,用來 setTimeout var timer // 返回一個函數(shù),這個函數(shù)會在一個時間區(qū)間結(jié)束后的 delay 毫秒時執(zhí)行 fn 函數(shù) return function () { // 保存函數(shù)調(diào)用時的上下文和參數(shù),傳遞給 fn var context = this var args = arguments // 每次這個返回的函數(shù)被調(diào)用,就清除定時器,以保證不執(zhí)行 fn clearTimeout(timer) // 當(dāng)返回的函數(shù)被最后一次調(diào)用后(也就是用戶停止了某個連續(xù)的操作), // 再過 delay 毫秒就執(zhí)行 fn timer = setTimeout(function () { fn.apply(context, args) }, delay) } }
【完整版】
// immediate: 是否立即執(zhí)行回調(diào)函數(shù); 其它參數(shù)同上 function debounce(fn, wait, immediate) { let timer = null return function() { let args = [].slice.call(arguments) if (immediate && !timer) { fn.apply(this, args) } if (timer) clearTimeout(timer) timer = setTimeout(() => { //箭頭函數(shù),this指向外層環(huán)境 fn.apply(this, args) }, wait) } }
// 測試: var fn = function() { console.log("debounce..") } oDiv.addEventListener("click", debounce(fn, 3000))節(jié)流 throttle
固定頻次:減少執(zhí)行頻次,每隔一定時間執(zhí)行一次
【簡潔版】
/** * 固定回調(diào)函數(shù)執(zhí)行的頻次 * @param fn {Function} 實際要執(zhí)行的函數(shù) * @param interval {Number} 執(zhí)行間隔,單位是毫秒(ms) * * @return {Function} 返回一個“節(jié)流”函數(shù) */ var throttle = function (fn, interval) { // 記錄前一次時間 var last = +new Date() var timer = null // 包裝完后返回 閉包函數(shù) return function () { var current = +new Date() var args = [].slice.call(arguments, 0) var context = this // 首先清除定時器 clearTimeout(timer) // current 與last 間隔大于interval 執(zhí)行一次fn // 在一個周期內(nèi) last相對固定 current一直再增加 // 這里可以保證調(diào)用很密集的情況下 current和last 必須是相隔interval 才會調(diào)用fn if (current - last >= interval) { fn.apply(context, args) last = current } else { // 如果沒有大于間隔 添加定時器 // 這可以保證 即使后面沒有再次觸發(fā) fn也會在規(guī)定的interval后被調(diào)用 timer = setTimeout(function() { fn.apply(context, args) last = current }, interval-(current - last)) } } }
【完整版】
/** * 頻率控制 返回函數(shù)連續(xù)調(diào)用時,func 執(zhí)行頻率限定為 次 / wait * 自動合并 data * * 若無 option 選項,或者同時為true,即 option.trailing !== false && option.leading !== false,在固定時間開始時刻調(diào)用一次回調(diào),并每個固定時間最后時刻調(diào)用回調(diào) * 若 option.trailing !== false && option.leading === false, 每個固定時間最后時刻調(diào)用回調(diào) * 若 option.trailing === false && option.leading !== false, 只會在固定時間開始時刻調(diào)用一次回調(diào) * 若同時為false 則不會被調(diào)用 * * @param {function} func 傳入函數(shù) * @param {number} wait 表示時間窗口的間隔 * @param {object} options 如果想忽略開始邊界上的調(diào)用,傳入{leading: false}。默認(rèn)undefined * 如果想忽略結(jié)尾邊界上的調(diào)用,傳入{trailing: false}, 默認(rèn)undefined * @return {function} 返回客戶調(diào)用函數(shù) */ function throttle (func, wait, options) { var context, args, result; var timeout = null; // 上次執(zhí)行時間點 var previous = 0; if (!options) { options = {}; } // 延遲執(zhí)行函數(shù) function later () { // 若設(shè)定了開始邊界不執(zhí)行選項,上次執(zhí)行時間始終為0 previous = options.leading === false ? 0 : Date.now(); timeout = null; result = func.apply(context, args); if (!timeout) { context = args = null; } } return function (handle, data) { var now = Date.now(); // 首次執(zhí)行時,如果設(shè)定了開始邊界不執(zhí)行選項,將上次執(zhí)行時間設(shè)定為當(dāng)前時間。 if (!previous && options.leading === false) { previous = now; } // 延遲執(zhí)行時間間隔 var remaining = wait - (now - previous); context = this; args = args ? [handle, Object.assign(args[1], data)] : [handle, data]; // 延遲時間間隔remaining小于等于0,表示上次執(zhí)行至此所間隔時間已經(jīng)超過一個時間窗口 // remaining大于時間窗口wait,表示客戶端系統(tǒng)時間被調(diào)整過 if (remaining <= 0 || remaining > wait) { clearTimeout(timeout); timeout = null; previous = now; result = func.apply(context, args); if (!timeout) { context = args = null; } // 如果延遲執(zhí)行不存在,且沒有設(shè)定結(jié)尾邊界不執(zhí)行選項 } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result } }運用
游戲射擊,keydown 事件
文本輸入、自動完成,keyup 事件
鼠標(biāo)移動,mousemove 事件
DOM 元素動態(tài)定位,window 對象的 resize 和 scroll 事件
前兩者 debounce 和 throttle 都可以按需使用;后兩者肯定是用 throttle
underscore 實現(xiàn)源碼 debounce_.debounce = function(func, wait, immediate) { var timeout, result; var later = function(context, args) { timeout = null; if (args) result = func.apply(context, args); }; var debounced = restArgs(function(args) { if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(later, wait); if (callNow) result = func.apply(this, args); } else { timeout = _.delay(later, wait, this, args); } return result; }); debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; };throttle
_.throttle = function(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; };
【參考】
https://blog.coding.net/blog/...
https://github.com/lishengzxc...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/105533.html
摘要:上段代碼的一個問題是,事件會在定時器結(jié)束后被觸發(fā),因此會出現(xiàn)一定的延遲,如果想讓事件被立即觸發(fā),可以使用以下的去抖函數(shù)但是,對于去抖來說,在某些場景下是不合適的,因此我們可以使用節(jié)流。 參考文章游戲星人眼中的節(jié)流與去抖(很生動) 函數(shù)去抖與節(jié)流 Debounce:函數(shù)去抖就是對于一定時間段的連續(xù)的函數(shù)調(diào)用,只讓其執(zhí)行一次Throttle:函數(shù)節(jié)流就是讓連續(xù)執(zhí)行的函數(shù),變成固定時間段間斷...
摘要:可以看下面的栗子這個圖中圖中每個小格大約,右邊有原生事件與節(jié)流去抖插件的與事件。即如果有連續(xù)不斷的觸發(fā),每執(zhí)行一次,用在每隔一定間隔執(zhí)行回調(diào)的場景。執(zhí)行啦打印執(zhí)行啦打印執(zhí)行啦節(jié)流按照上面的說明,節(jié)流就是連續(xù)多次內(nèi)的操作按照指定的間隔來執(zhí)行。 一般在項目中我們會對input、scroll、resize等事件進(jìn)行節(jié)流控制,防止事件過多觸發(fā),減少資源消耗;在vue的官網(wǎng)的例子中就有關(guān)于lod...
摘要:去抖主要針對的是頻繁觸發(fā)某個事件后,然后進(jìn)行后續(xù)處理的場景。常見的就是頻繁輸入停止假設(shè)后進(jìn)行查詢等操作。函數(shù)接口定義實際需要調(diào)用的函數(shù)空閑時間返回調(diào)用函數(shù)函數(shù)接口定義延遲時間需要調(diào)用的函數(shù)返回函數(shù) 前言 做過前端的童鞋應(yīng)該都知道lodash這個強(qiáng)大的使用工具庫。為什么要寫這篇文章呢,主要今天遇到一個問題,socket推送消息太頻繁,導(dǎo)致saga頻繁更新,頁面有所卡頓,需要通過函數(shù)節(jié)流控...
摘要:函數(shù)節(jié)流和去抖的出現(xiàn)場景,一般都伴隨著客戶端的事件監(jiān)聽。函數(shù)節(jié)流的核心是,讓一個函數(shù)不要執(zhí)行得太頻繁,減少一些過快的調(diào)用來節(jié)流。 概述 也是好久沒更新 源碼解讀,看著房價蹭蹭暴漲,心里也是五味雜陳,對未來充滿恐懼和迷茫 ...(敢問一句你們上岸了嗎) 言歸正傳,今天要介紹的是 underscore 中兩個重要的方法,函數(shù)節(jié)流和函數(shù)去抖。這篇文章不會涉及具體的代碼實現(xiàn)(關(guān)于代碼實現(xiàn)請期...
摘要:節(jié)流保證在一定時間內(nèi),只能觸發(fā)一次。我們在嘗試一下去抖消抖,消除抖動,感覺這個更好聽有沒有什么現(xiàn)成的上的一次發(fā)現(xiàn)源碼的經(jīng)歷以及對學(xué)術(shù)界拿來主義的思考函數(shù)節(jié)流和函數(shù)去抖應(yīng)用場景辨析函數(shù)去抖的實現(xiàn) 開篇先提幾個問題? 1.做搜索框的時候你使用什么事件?change?blur?keyup?你想要的效果是什么? 2.scroll事件怎么就觸發(fā)?是滾一段距離觸發(fā)一次?還是滾一圈觸發(fā)一次?還是滾...
閱讀 2468·2021-11-19 09:59
閱讀 1991·2019-08-30 15:55
閱讀 935·2019-08-29 13:30
閱讀 1336·2019-08-26 10:18
閱讀 3087·2019-08-23 18:36
閱讀 2388·2019-08-23 18:25
閱讀 1161·2019-08-23 18:07
閱讀 438·2019-08-23 17:15