日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

探究防抖(debounce)和节流(throttle)

發布時間:2025/4/5 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 探究防抖(debounce)和节流(throttle) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文來自我的博客,歡迎大家去GitHub上star我的博客

本文從防抖和節流出發,分析它們的特性,并拓展一種特殊的節流方式requestAnimationFrame,最后對lodash中的debounce源碼進行分析

防抖和節流是前端開發中經常使用的一種優化手段,它們都被用來控制一段時間內方法執行的次數,可以為我們節省大量不必要的開銷

防抖(debounce)

當我們需要及時獲知窗口大小變化時,我們會給window綁定一個resize函數,像下面這樣:

window.addEventListener('resize', () => {console.log('resize') });

我們會發現,即使是極小的縮放操作,也會打印數十次resize,也就是說,如果我們需要在onresize函數中搞一些小動作,也會重復執行幾十次。但實際上,我們只關心鼠標松開,窗口停止變化的那一次resize,這時候,就可以使用debounce優化這個過程:

const handleResize = debounce(() => {console.log('resize'); }, 500); window.addEventListener('resize', handleResize);

運行上面的代碼(你得有現成的debounce函數),在停止縮放操作500ms后,默認用戶無繼續操作了,才會打印resize

這就是防抖的功效,它把一組連續的調用變為了一個,最大程度地優化了效率

再舉一個防抖的常見場景:

搜索欄常常會根據我們的輸入,向后端請求,獲取搜索候選項,顯示在搜索欄下方。如果我們不使用防抖,在輸入“debounce”時前端會依次向后端請求"d"、"de"、"deb"..."debounce"的搜索候選項,在用戶輸入很快的情況下,這些請求是無意義的,可以使用防抖優化

觀察上面這兩個例子,我們發現,防抖非常適于只關心結果,不關心過程如何的情況,它能很好地將大量連續事件轉為單個我們需要的事件

為了更好理解,下面提供了最簡單的debounce實現:返回一個function,第一次執行這個function會啟動一個定時器,下一次執行會清除上一次的定時器并重起一個定時器,直到這個function不再被調用,定時器成功跑完,執行回調函數

const debounce = function(func, wait) {let timer;return function() {!!timer && clearTimeout(timer);timer = setTimeout(func, wait);}; };

那如果我們不僅關心結果,同時也關心過程呢?

節流(throttle)

節流讓指定函數在規定的時間里執行次數不會超過一次,也就是說,在連續高頻執行中,動作會被定期執行。節流的主要目的是將原本操作的頻率降低

實例:

我們模擬一個可無限滾動的feed流

html:

<div id="wrapper"><div class="feed"></div><div class="feed"></div><div class="feed"></div><div class="feed"></div><div class="feed"></div> </div>

css:

#wrapper {height: 500px;overflow: auto; } .feed {height: 200px;background: #ededed;margin: 20px; }

js:

const wrapper = document.getElementById("wrapper"); const loadContent = () => {const {scrollHeight,clientHeight,scrollTop} = wrapper;const heightFromBottom = scrollHeight - scrollTop - clientHeight;if (heightFromBottom < 200) {const wrapperCopy = wrapper.cloneNode(true);const children = [].slice.call(wrapperCopy.children);children.forEach(item => {wrapper.appendChild(item);})} } const handleScroll = throttle(loadContent, 200); wrapper.addEventListener("scroll", handleScroll);

可以看到,在這個例子中,我們需要不停地獲取滾動條距離底部的高度,以判斷是否需要增加新的內容。我們知道,srcoll同樣也是種會高頻觸發的事件,我們需要減少它有效觸發的次數。如果使用的是防抖,那么得等我們停止滾動之后一段時間才會加載新的內容,沒有那種無限滾動的流暢感。這時候,我們就可以使用節流,將事件有效觸發的頻率降低的同時給用戶流暢的瀏覽體驗。在這個例子中,我們指定throttle的wait值為200ms,也就是說,如果你一直在滾動頁面,loadCotent函數也只會每200ms執行一次

同樣,這里有throttle最簡單的實現,當然,這種實現很粗糙,有不少缺陷(比如沒有考慮最后一次執行),只供初步理解使用:

const throttle = function (func, wait) {let lastTime;return function () {const curTime = Date.now();if (!lastTime || curTime - lastTime >= wait) {lastTime = curTime;return func();}} }

requestAnimationFrame(rAF)

rAF在一定程度上和throttle(func,16)的作用相似,但它是瀏覽器自帶的api,所以,它比throttle函數執行得更加平滑。調用window.requestAnimationFrame(),瀏覽器會在下次刷新的時候執行指定回調函數。通常,屏幕的刷新頻率是60hz,所以,這個函數也就是大約16.7ms執行一次。如果你想讓你的動畫更加平滑,用rAF就再好不過了,因為它是跟著屏幕的刷新頻率來的

rAF的寫法與debounce和throttle不同,如果你想用它繪制動畫,需要不停地在回調函數里調用自身,具體寫法可以參考mdn

rAF支持ie10及以上瀏覽器,不過因為是瀏覽器自帶的api,我們也就無法在node中使用它了

總結

debounce將一組事件的執行轉為最后一個事件的執行,如果你只關注結果,debounce再適合不過

如果你同時關注過程,可以使用throttle,它可以用來降低高頻事件的執行頻率

如果你的代碼是在瀏覽器上運行,不考慮兼容ie10,并且要求頁面上的變化盡可能的平滑,可以使用rAF

參考:https://css-tricks.com/debouncing-throttling-explained-examples/

附:lodash源碼解析

lodash的debounce功能十分強大,集debounce、throttle和rAF于一身,所以我特意研讀一下,下面是我的解析(我刪去了一些不重要的代碼,比如debounced的cancel方法):

function debounce(func, wait, options) {/*** lastCallTime是上一次執行debounced函數的時間* lastInvokeTime是上一次調用func的時間*/let lastArgs, lastThis, maxWait, result, timerId, lastCallTime;let lastInvokeTime = 0;let leading = false;let maxing = false;let trailing = true;/*** 如果沒設置wait且raf可用 則默認使用raf*/const useRAF =!wait && wait !== 0 && typeof root.requestAnimationFrame === "function";if (typeof func !== "function") {throw new TypeError("Expected a function");}wait = +wait || 0;if (isObject(options)) {leading = !!options.leading;maxing = "maxWait" in options;maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait;trailing = "trailing" in options ? !!options.trailing : trailing;}/*** 執行func*/function invokeFunc(time) {const args = lastArgs;const thisArg = lastThis;lastArgs = lastThis = undefined;/*** 更新lastInvokeTime*/lastInvokeTime = time;result = func.apply(thisArg, args);return result;}/*** 調用定時器*/function startTimer(pendingFunc, wait) {if (useRAF) {root.cancelAnimationFrame(timerId);return root.requestAnimationFrame(pendingFunc);}return setTimeout(pendingFunc, wait);}/*** 在每輪debounce開始調用*/function leadingEdge(time) {lastInvokeTime = time;timerId = startTimer(timerExpired, wait);return leading ? invokeFunc(time) : result;}/*** 計算剩余時間* 1是 wait 減去 距離上次調用debounced時間(lastCallTime)* 2是 maxWait 減去 距離上次調用func時間(lastInvokeTime)* 1和2取最小值*/function remainingWait(time) {const timeSinceLastCall = time - lastCallTime;const timeSinceLastInvoke = time - lastInvokeTime;const timeWaiting = wait - timeSinceLastCall;return maxing? Math.min(timeWaiting, maxWait - timeSinceLastInvoke): timeWaiting;}/*** 判斷是否需要執行*/function shouldInvoke(time) {const timeSinceLastCall = time - lastCallTime;const timeSinceLastInvoke = time - lastInvokeTime;/*** 4種情況返回true,否則返回false* 1.第一次調用* 2.距離上次調用debounced時間(lastCallTime)>=wait* 3.系統時間倒退* 4.設置了maxWait,距離上次調用func時間(lastInvokeTime)>=maxWait*/return (lastCallTime === undefined ||timeSinceLastCall >= wait ||timeSinceLastCall < 0 ||(maxing && timeSinceLastInvoke >= maxWait));}/*** 通過shouldInvoke函數判斷是否執行* 執行:調用trailingEdge函數* 不執行:調用startTimer函數重新開始timer,wait值通過remainingWait函數計算*/function timerExpired() {const time = Date.now();if (shouldInvoke(time)) {return trailingEdge(time);}// Restart the timer.timerId = startTimer(timerExpired, remainingWait(time));}/*** 在每輪debounce結束調用*/function trailingEdge(time) {timerId = undefined;/*** trailing為true且lastArgs不為undefined時調用*/if (trailing && lastArgs) {return invokeFunc(time);}lastArgs = lastThis = undefined;return result;}function debounced(...args) {const time = Date.now();const isInvoking = shouldInvoke(time);lastArgs = args;lastThis = this;/*** 更新lastCallTime*/lastCallTime = time;if (isInvoking) {/*** 第一次調用*/if (timerId === undefined) {return leadingEdge(lastCallTime);}/*** 【注1】*/if (maxing) {timerId = startTimer(timerExpired, wait);return invokeFunc(lastCallTime);}}/*** 【注2】*/if (timerId === undefined) {timerId = startTimer(timerExpired, wait);}return result;}return debounced; }

推薦是從返回的方法debounced開始,順著執行順序閱讀,理解起來更輕松

【注1】一開始我沒看明白if(maxing)里面這段代碼的作用,按理說,是不會執行這段代碼的,后來我去lodash的倉庫里看了test文件,發現對這段代碼,專門有一個case對其測試。我剝除了一些代碼,并修改了測試用例以便展示,如下:

var limit = 320,withCount = 0var withMaxWait = debounce(function () {console.log('invoke');withCount++; }, 64, {'maxWait': 128 });var start = +new Date; while ((new Date - start) < limit) {withMaxWait(); }

執行代碼,打印了3次invoke;我又將if(maxing){}這段代碼注釋,再執行代碼,結果只打印了1次。結合源碼的英文注釋Handle invocations in a tight loop,我們不難理解,原本理想的執行順序是withMaxWait->timer->withMaxWait->timer這種交替進行,但由于setTimeout需等待主線程的代碼執行完畢,所以這種短時間快速調用就會導致withMaxWait->withMaxWait->timer->timer,從第二個timer開始,由于lastArgs被置為undefined,也就不會再調用invokeFunc函數,所以只會打印一次invoke。

同時,由于每次執行invokeFunc時都會將lastArgs置為undefined,在執行trailingEdge時會對lastArgs進行判斷,確保不會出現執行了if(maxing){}中的invokeFunc函數又執行了timer的invokeFunc函數

這段代碼保證了設置maxWait參數后的正確性和時效性

【注2】執行過一次trailingEdge后,再執行debounced函數,可能會遇到shouldInvoke返回false的情況,需單獨處理

【注3】對于lodash的debounce來說,throttle是一種leading為true且maxWait等于wait的特殊debounce

總結

以上是生活随笔為你收集整理的探究防抖(debounce)和节流(throttle)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 国产精品露脸视频 | 国产情侣小视频 | 亚洲欧美天堂 | 欧美乱妇高清无乱码 | 亚洲国产精品av | 91视频最新地址 | 毛片导航| 一区二区三区免费毛片 | 欧美一区二区三区免 | 日韩免费三级 | 免费播放片大片 | 午夜中出 | 精品国产乱码久久久久久蜜柚 | 亚洲一级网站 | 天海翼视频在线观看 | 成人免费毛片高清视频 | 久久99热久久99精品 | 日日麻批免费视频播放 | 91精品国自产在线偷拍蜜桃 | 手机看片1024久久 | 久久高清无码电影 | 日日干夜夜爽 | 欧美一区二区视频在线 | 青青久在线 | 亚洲国产日韩一区二区 | 一级大片免费观看 | 欧美激情在线观看 | 美女扒开尿口给男人看 | 国产女18毛片多18精品 | 亚洲中文字幕无码不卡电影 | 干老太太视频 | 成人免费在线电影 | 亚洲红桃视频 | www国产免费 | 操欧美女人 | 一区二区三区日本视频 | 国产视频在线免费观看 | 欧美第一页草草影院 | 名人明星三级videos | 成年网站免费在线观看 | 免费观看日韩av | 污污小说在线观看 | 视频在线观看你懂的 | 秘密基地电影免费版观看国语 | 欧美高清在线一区 | 熟妇高潮精品一区二区三区 | 亚洲天堂一区二区在线观看 | 中国在线观看视频高清免费 | 香蕉视频黄色在线观看 | 久久国产精品久久久久久电车 | 精品久久久一区二区 | 91福利免费视频 | 叶山小百合av一区二区 | 国产真实偷伦视频 | 六十路息与子猛烈交尾 | 久久久一| 国产亚洲av片在线观看18女人 | 亚洲精品一区二三区不卡 | 国产又大又黑又粗 | 国产一区二区在线免费观看视频 | 国产又粗又长又大 | 色一情一交一乱一区二区三区 | 无码精品a∨在线观看中文 福利片av | 国产精品成人电影在线观看 | 少妇激情偷人三级 | 国产精品成人电影在线观看 | 在线观看视频一区 | 欧美情趣视频 | 无码免费一区二区三区免费播放 | 人妻在线日韩免费视频 | 日韩精品一二三区 | 女女同性被吸乳羞羞 | 伊人福利在线 | 欧美日韩成人 | 久久99热这里只频精品6学生 | 日本午夜精品理论片a级app发布 | 国产在线1 | 黄色的网站在线观看 | 一级黄色大片网站 | 亚洲AV无码精品国产 | 国产一区二区毛片 | 视频一区二区三区四区五区 | 中文视频在线观看 | 孕妇疯狂做爰xxxⅹ 国产精品乱码久久久久久 99久久久成人国产精品 | 超碰97在线资源站 | 91看片看淫黄大片 | 国产99在线播放 | 国产三级在线看 | 久久精品视频免费观看 | 亚洲av男人的天堂在线观看 | 欧美s码亚洲码精品m码 | 久久调教视频 | 88福利视频 | 日韩欧美一区二区三区视频 | 337p粉嫩大胆噜噜噜亚瑟影院 | 欧洲精品久久久久毛片完整版 | 久久国产精品网 | 亚洲国产一区二区a毛片 | 蜜桃臀av|