摘要:如果想忽略結(jié)尾邊界上的調(diào)用,傳入返回客戶調(diào)用函數(shù)上次執(zhí)行時間點延遲執(zhí)行函數(shù)若設(shè)定了開始邊界不執(zhí)行選項,上次執(zhí)行時間始終為首次執(zhí)行時,如果設(shè)定了開始邊界不執(zhí)行選項,將上次執(zhí)行時間設(shè)定為當(dāng)前時間。
文章轉(zhuǎn)自:https://blog.coding.net/blog/...
注: _.throttle 和 _.debounce是Underscore.js庫的兩個針對函數(shù)節(jié)流的方法,用于處理高頻率觸發(fā)的事件,是setTimeout的優(yōu)化。
以此為契機(jī),在以后的系列文章,會和大家一塊解讀underscope.js
Underscore.js是一個很精干的庫,壓縮后只有5.2KB。它提供了幾十種函數(shù)式編程的方法,彌補(bǔ)了標(biāo)準(zhǔn)庫的不足,大大方便了JavaScript的編程。
本文僅探討Underscore.js的兩個函數(shù)方法 _.throttle 和 _.debounce 的原理、效果和用途。
通常的函數(shù)(或方法)調(diào)用過程分為三個部分:請求、執(zhí)行和響應(yīng)。(文中“請求”與“調(diào)用”同義,“響應(yīng)”與“返回”同義,為了更好的表述,刻意采用請求和響應(yīng)的說法。)
某些場景下,比如響應(yīng)鼠標(biāo)移動或者窗口大小調(diào)整的事件,觸發(fā)頻率比較高。若稍處理函數(shù)微復(fù)雜,需要較多的運算執(zhí)行時間,響應(yīng)速度跟不上觸發(fā)頻率,往往會出現(xiàn)延遲,導(dǎo)致假死或者卡頓感。
在運算資源不夠的時候,最直觀的解決辦法就是升級硬件,誠然通過購買更好的硬件可以解決部分問題,但是也需要為此付出高額的成本。特別是客戶端和服務(wù)器模式,要求客戶端統(tǒng)一升級硬件基本不可能。
在資源有限的前提下,處理函數(shù)無法即時響應(yīng)高頻調(diào)用。退而求其次,只響應(yīng)部分請求是否可行呢?某些場景下的密集性請求,具備很強(qiáng)的同質(zhì)和連續(xù)性。比如說,鼠標(biāo)移動的軌跡參數(shù)。響應(yīng)越及時效果越平滑,但是如果響應(yīng)速度跟不上時,反而會出現(xiàn)卡頓感,如果適當(dāng)?shù)膩G棄一些請求效果更流暢。
throttle 和 debounce不同之處:throttle 和 debounce 是解決請求和響應(yīng)速度不匹配問題的兩個方案。二者的差異在于選擇不同的策略。
電梯超時
想象每天上班大廈底下的電梯。把電梯完成一次運送,類比為一次函數(shù)的執(zhí)行和響應(yīng)。假設(shè)電梯有兩種運行策略 throttle 和 debounce ,超時設(shè)定為15秒,不考慮容量限制。
throttle 策略的電梯。保證如果電梯第一個人進(jìn)來后,15秒后準(zhǔn)時運送一次,不等待。如果沒有人,則待機(jī)。
debounce 策略的電梯。如果電梯里有人進(jìn)來,等待15秒。如果又人進(jìn)來,15秒等待重新計時,直到15秒超時,開始運送。
使用示例
_.throttle 使用示例 function log( event ) { console.log( $(window).scrollTop(), event.timeStamp ); }; // 控制臺記錄窗口滾動事件,觸發(fā)頻率比你想象的要快 $(window).scroll( log ); // 控制臺記錄窗口滾動事件,每250ms最多觸發(fā)一次 $(window).scroll( _.throttle( log, 250 ) ); _.debounce 使用示例 function ajax_lookup( event ) { // 對輸入的內(nèi)容$(this).val()執(zhí)行 Ajax 查詢 }; // 字符輸入的頻率比你預(yù)想的要快,Ajax 請求來不及回復(fù)。 $("input:text").keyup( ajax_lookup ); // 當(dāng)用戶停頓250毫秒以后才開始查找 $("input:text").keyup( _.debounce( ajax_lookup, 250 ) );underscore源碼注解
讓我們來讀讀源碼,探其究竟。基于開發(fā)版本(1.7.0)的源碼,加上了一些注釋以幫助理解。
_.throttle方法源碼 /** * 頻率控制 返回函數(shù)連續(xù)調(diào)用時,func 執(zhí)行頻率限定為 次 / wait * * @param {function} func 傳入函數(shù) * @param {number} wait 表示時間窗口的間隔 * @param {object} options 如果想忽略開始邊界上的調(diào)用,傳入{leading: false}。 * 如果想忽略結(jié)尾邊界上的調(diào)用,傳入{trailing: false} * @return {function} 返回客戶調(diào)用函數(shù) */ _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; // 上次執(zhí)行時間點 var previous = 0; if (!options) options = {}; // 延遲執(zhí)行函數(shù) var later = function() { // 若設(shè)定了開始邊界不執(zhí)行選項,上次執(zhí)行時間始終為0 previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = _.now(); // 首次執(zhí)行時,如果設(shè)定了開始邊界不執(zhí)行選項,將上次執(zhí)行時間設(shè)定為當(dāng)前時間。 if (!previous && options.leading === false) previous = now; // 延遲執(zhí)行時間間隔 var remaining = wait - (now - previous); context = this; args = arguments; // 延遲時間間隔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; }; }; _.debounce方法源碼 /** * 空閑控制 返回函數(shù)連續(xù)調(diào)用時,空閑時間必須大于或等于 wait,func 才會執(zhí)行 * * @param {function} func 傳入函數(shù) * @param {number} wait 表示時間窗口的間隔 * @param {boolean} immediate 設(shè)置為ture時,調(diào)用觸發(fā)于開始邊界而不是結(jié)束邊界 * @return {function} 返回客戶調(diào)用函數(shù) */ _.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { // 據(jù)上一次觸發(fā)時間間隔 var last = _.now() - timestamp; // 上次被包裝函數(shù)被調(diào)用時間間隔last小于設(shè)定時間間隔wait if (last < wait && last > 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; // 如果設(shè)定為immediate===true,因為開始邊界已經(jīng)調(diào)用過了此處無需調(diào)用 if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; // 如果延時不存在,重新設(shè)定延時 if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; };可視化演示
Throtte & Debounce 可視化演示
示例中每一行都以30ms的速度繪制時間軸,第一行Mousemove Events是參考基準(zhǔn),以50ms每次的響應(yīng)頻率,在時間軸上輸出循環(huán)可見ASCII碼字符。
當(dāng)鼠標(biāo)進(jìn)入左側(cè)方型區(qū)域(mouseenter 事件)所有行開始繪制時間軸, 鼠標(biāo)晃動(mousemove 事件)會在時間軸上繪制字符塊,每個字符塊表示事件被觸發(fā)一次。為了展現(xiàn)延遲觸發(fā)效果,相鄰字符塊的演示和文字是不同的。
頂部的兩個按鈕每100毫秒觸發(fā)1次和每200毫秒觸發(fā)2次演示以固定頻率勻速觸發(fā)事件的效果。
演示地址:http://throttle-debounce.codi...
源碼地址:https://coding.net/u/duwan/p/...
使用場景
只要牽涉到連續(xù)事件或頻率控制相關(guān)的應(yīng)用都可以考慮到這兩個函數(shù),比如:
游戲射擊,keydown 事件
文本輸入、自動完成,keyup 事件
鼠標(biāo)移動,mousemove 事件
DOM 元素動態(tài)定位,window 對象的 resize 和 scroll 事件
前兩者 debounce 和 throttle 都可以按需使用;后兩者肯定是用 throttle 了。如果不做過濾處理,每秒種甚至?xí)|發(fā)數(shù)十次相應(yīng)的事件。尤其是 mousemove 事件,每移動一像素都可能觸發(fā)一次事件。如果是在一個畫布上做一個鼠標(biāo)相關(guān)的應(yīng)用,過濾事件處理是必須的,否則肯定會造成糟糕的體驗。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/80842.html
摘要:如果想忽略結(jié)尾邊界上的調(diào)用,傳入返回客戶調(diào)用函數(shù)上次執(zhí)行時間點延遲執(zhí)行函數(shù)若設(shè)定了開始邊界不執(zhí)行選項,上次執(zhí)行時間始終為首次執(zhí)行時,如果設(shè)定了開始邊界不執(zhí)行選項,將上次執(zhí)行時間設(shè)定為當(dāng)前時間。 Underscore.js 是一個很精干的庫,壓縮后只有5.2KB。它提供了幾十種函數(shù)式編程的方法,彌補(bǔ)了標(biāo)準(zhǔn)庫的不足,大大方便了JavaScript的編程。 本文僅探討Unde...
摘要:舉例舉例通過拖拽瀏覽器窗口,可以觸發(fā)很多次事件。不支持,所以不能在服務(wù)端用于文件系統(tǒng)事件。總結(jié)將一系列迅速觸發(fā)的事件例如敲擊鍵盤合并成一個單獨的事件。確保一個持續(xù)的操作流以每毫秒執(zhí)行一次的速度執(zhí)行。 Debounce 和 Throttle 是兩個很相似但是又不同的技術(shù),都可以控制一個函數(shù)在一段時間內(nèi)執(zhí)行的次數(shù)。 當(dāng)我們在操作 DOM 事件的時候,為函數(shù)添加 debounce 或者 th...
摘要:自己嘗試一下年在的文章中第一次看到的實現(xiàn)方法。這三種實現(xiàn)方法內(nèi)部不同,但是接口幾乎一致。如你所見,我們使用了參數(shù),因為我們只對用戶停止改變?yōu)g覽器大小時最后一次事件感興趣。 前幾天看到一篇文章,我的公眾號里也分享了《一次發(fā)現(xiàn)underscore源碼bug的經(jīng)歷以及對學(xué)術(shù)界拿來主義的思考》具體文章詳見,微信公眾號:showImg(https://segmentfault.com/img/b...
摘要:用局部變量存儲本地范圍之外的變量值,如果它們在函數(shù)中的使用多于一次。將它的值存入一個局部變量,消除一次搜索過程。地將此值存入一個局部變量中。 總結(jié)了一下《高性能javascript》書中比較核心的點,并補(bǔ)充了一些點。 第一章 DOM標(biāo)簽 將所有 標(biāo)簽放置在頁面的底部,緊靠 body 關(guān)閉標(biāo)簽的上方。此法可以保證頁面在腳本 運行之前完成解析。 將腳本成組打包。頁面的 標(biāo)簽越少,頁面的加...
摘要:譯通過實例講解和防抖與節(jié)流源碼中推薦的文章,為了學(xué)習(xí)英語,翻譯了一下原文鏈接作者本文來自一位倫敦前端工程師的技術(shù)投稿。首次或立即你可能發(fā)現(xiàn)防抖事件在等待觸發(fā)事件執(zhí)行,直到事件都結(jié)束后它才執(zhí)行。 [譯]通過實例講解Debouncing和Throtting(防抖與節(jié)流) lodash源碼中推薦的文章,為了學(xué)習(xí)(英語),翻譯了一下~ 原文鏈接 作者:DAVID CORBACHO 本文來自一位...
閱讀 3988·2021-11-22 15:31
閱讀 2524·2021-11-18 13:20
閱讀 3109·2021-11-15 11:37
閱讀 7022·2021-09-22 15:59
閱讀 744·2021-09-13 10:27
閱讀 3778·2021-09-09 09:33
閱讀 1443·2019-08-30 15:53
閱讀 2569·2019-08-29 15:37