摘要:那么還有最后一個(gè)問題,那我之前設(shè)置的定時(shí)器怎么辦呢定時(shí)器執(zhí)行的是這個(gè)函數(shù),而這個(gè)函數(shù)又會(huì)通過進(jìn)行一次判斷。
我們?cè)谔幚硎录臅r(shí)候,有些事件由于觸發(fā)太頻繁,而每次事件都處理的話,會(huì)消耗太多資源,導(dǎo)致瀏覽器崩潰。最常見的是我們?cè)谝苿?dòng)端實(shí)現(xiàn)無限加載的時(shí)候,移動(dòng)端本來滾動(dòng)就不是很靈敏,如果每次滾動(dòng)都處理的話,界面就直接卡死了。
因此,我們通常會(huì)選擇,不立即處理事件,而是在觸發(fā)一定次數(shù)或一定時(shí)間之后進(jìn)行處理。這時(shí)候我們有兩個(gè)選擇: debounce(防抖動(dòng))和 throttle(節(jié)流閥)。
之前看過很多文章都還是沒有太弄明白兩者之間的區(qū)別,最后通過看源碼大致了解了兩者之間的區(qū)別以及簡單的實(shí)現(xiàn)思路。
首先,我們通過實(shí)踐來最簡單的看看二者的區(qū)別:
可以看到,throttle會(huì)在第一次事件觸發(fā)的時(shí)候就執(zhí)行,然后每隔wait(我這里設(shè)置的2000ms)執(zhí)行一次,而debounce只會(huì)在事件結(jié)束之后執(zhí)行一次。
有了一個(gè)大概的印象之后,我們看一看lodash的源碼對(duì)debounce和throttle的區(qū)別。
這里討論默認(rèn)情況
function throttle(func, wait, options) { let leading = true, trailing = true; if (typeof func !== "function") { throw new TypeError(FUNC_ERROR_TEXT); } if (typeof options === "object") { leading = "leading" in options ? !!options.leading : leading; trailing = "trailing" in options ? !!options.trailing : trailing; } return debounce(func, wait, { leading, maxWait: wait, trailing, }); }
可以看到,throttle最后返回的還是debounce函數(shù),只是指定了options選項(xiàng)。那么接下來我們就集中分析debounce。
function debounce(fn, wait, options) { var lastArgs, lastThis, maxWait, result, timerId, lastCallTime, lastInvokeTime = 0, leading = false, maxing = false, trailing = true; function debounced() { var time = now(), isInvoking = shouldInvoke(time); lastArgs = arguments; lastThis = this; lastCallTime = time; if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime); } if (maxing) { // Handle invocations in a tight loop. timerId = setTimeout(timerExpired, wait); return invokeFunc(lastCallTime); } } if (timerId === undefined) { timerId = setTimeout(timerExpired, wait); } return result; } debounced.cancel = cancel; debounced.flush = flush; return debounced; }
為了記錄每次執(zhí)行的相關(guān)信息,debounce函數(shù)最后返回的是一個(gè)函數(shù),形成一個(gè)閉包。
這也解釋了為什么這樣寫不行:
window.addEventListener("resize", function(){ _.debounce(onResize, 2000); });
這樣寫根本就不會(huì)調(diào)用內(nèi)部的debounced函數(shù)。
解決第一個(gè)不同在debounced內(nèi)部呢,首先記錄了當(dāng)前調(diào)用的時(shí)間,然后通過shouldInvoke這個(gè)函數(shù)判斷是否應(yīng)該調(diào)用傳入的func。
function shouldInvoke(time) { var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime; // Either this is the first call, activity has stopped and we"re at the // trailing edge, the system time has gone backwards and we"re treating // it as the trailing edge, or we"ve hit the `maxWait` limit. return (lastCallTime === undefined || (timeSinceLastCall >= wait) || (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); }
可以看到,該函數(shù)返回true的幾個(gè)條件。其中需要我們引起注意的是最后一個(gè)條件,這是debounce 與throttle的區(qū)別之一。
首先maxing通過函數(shù)開始的幾行代碼判斷:
if (isObject(options)) { leading = !!options.leading; maxing = "maxWait" in options; maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; trailing = "trailing" in options ? !!options.trailing : trailing; }
我們看到,在定義throttle的時(shí)候, 給debounce函數(shù)給傳入了options, 而里面包含maxWait這個(gè)屬性,因此,對(duì)于throttle來說,maxing為true, 而沒有傳入options的debounce則為false。這就是二者區(qū)別之一。在這里決定了shouldInvoke函數(shù)返回的值,以及是否執(zhí)行接下去的邏輯判斷。
我們?cè)倩氐?b>debounced這個(gè)函數(shù):
if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime); } if (maxing) { // Handle invocations in a tight loop. timerId = setTimeout(timerExpired, wait); return invokeFunc(lastCallTime); } } if (timerId === undefined) { timerId = setTimeout(timerExpired, wait); }
在第一次調(diào)用的時(shí)候,debounce 和 throttle 的 isInvoking
為true, 且此時(shí)timerId === undefined也成立,就返回leadingEdge(lastCallTime)這個(gè)函數(shù)。
那么我們?cè)賮砜纯?b>leadingEdge 這個(gè)函數(shù);
function leadingEdge(time) { // Reset any `maxWait` timer. lastInvokeTime = time; // Start the timer for the trailing edge. timerId = setTimeout(timerExpired, wait); // Invoke the leading edge. return leading ? invokeFunc(time) : result; }
這里出現(xiàn)了debounce和throttle的第二個(gè)區(qū)別。這個(gè)函數(shù)首先是設(shè)置了一個(gè)定時(shí)器,隨后返回的結(jié)果由leading決定。在默認(rèn)情況下,throttle傳入的leading為true,而debounce為false。因此,throttle會(huì)馬上執(zhí)行傳入的函數(shù),而debounce不會(huì)。
這里我們就解決了它們的第一個(gè)不同:throttle會(huì)在第一次調(diào)用的時(shí)候就執(zhí)行,而debounce不會(huì)。
解決第二個(gè)不同我們?cè)倩氐?b>shouldInvoke的返回條件那里,如果在一個(gè)時(shí)間內(nèi)頻繁的調(diào)用, 前面三個(gè)條件都不會(huì)成立,對(duì)于debounce來說,最后一個(gè)也不會(huì)成立。而對(duì)于throttle來說,首先maxing為true, 而如果距離上一次*傳入的func 函數(shù)調(diào)用 大于maxWait最長等待時(shí)間的話,它也會(huì)返回true。
function shouldInvoke(time) { var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime; return (lastCallTime === undefined || (timeSinceLastCall >= wait) || (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); }
if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime); } if (maxing) { // Handle invocations in a tight loop. timerId = setTimeout(timerExpired, wait); return invokeFunc(lastCallTime); } }
那么在shouldInvoke成立之后,throttle會(huì)設(shè)置一個(gè)定時(shí)器,返回執(zhí)行傳入函數(shù)的結(jié)果。
這就是debounce 和 throttle 之間的第二個(gè)區(qū)別:throttle會(huì)保證你每隔一段時(shí)間都會(huì)執(zhí)行,而debounce不會(huì)。
那么還有最后一個(gè)問題,那我之前設(shè)置的定時(shí)器怎么辦呢?
timerId = setTimeout(timerExpired, wait);
定時(shí)器執(zhí)行的是timerExpired這個(gè)函數(shù),而這個(gè)函數(shù)又會(huì)通過shouldInvoke進(jìn)行一次判斷。
function timerExpired() { var time = now(); if (shouldInvoke(time)) { return trailingEdge(time); } // Restart the timer. timerId = setTimeout(timerExpired, remainingWait(time)); }
最后,傳入的func怎么執(zhí)行的呢?下面這個(gè)函數(shù)實(shí)現(xiàn):
function invokeFunc(time) { var args = lastArgs, thisArg = lastThis; lastArgs = lastThis = undefined; lastInvokeTime = time; result = func.apply(thisArg, args); return result; }餓了么的簡單實(shí)現(xiàn)
在看餓了么的infinite scroll這個(gè)源碼的時(shí)候,看到了一個(gè)簡單版本的實(shí)現(xiàn):
var throttle = function (fn, delay) { var now, lastExec, timer, context, args; var execute = function () { fn.apply(context, args); lastExec = now; }; return function () { context = this; args = arguments; now = Date.now(); if (timer) { clearTimeout(timer); timer = null; } if (lastExec) { var diff = delay - (now - lastExec); if (diff < 0) { execute(); } else { timer = setTimeout(() => { execute(); }, diff); } } else { execute(); } }; };
那么它的思路很簡單:
通過lastExec判斷是否是第一次調(diào)用,如果是,就馬上執(zhí)行處理函數(shù)。
隨后就會(huì)監(jiān)測,每次調(diào)用的時(shí)間與上次執(zhí)行函數(shù)的時(shí)間差,如果小于0,就立馬執(zhí)行。大于0就會(huì)在事件間隔之后執(zhí)行。
每次調(diào)用的時(shí)候都會(huì)清除掉上一次的定時(shí)任務(wù),這樣就會(huì)保證只有一個(gè)最近的定時(shí)任務(wù)在等待執(zhí)行。
那么它與lodash的一個(gè)最大的區(qū)別呢,就是它是關(guān)注與上次執(zhí)行處理函數(shù)的時(shí)間差, 而lodash的shouldInvoke關(guān)注的是兩次事件調(diào)用函數(shù)的時(shí)間差。
總結(jié)總的來說,這種實(shí)現(xiàn)的主要部分呢,就是時(shí)間差 和 定時(shí)器
最后,自己參照寫了簡單的debounce 和 throttle: Gist求指教!
參考資料debouncing-throttling-explained-examples | CSS-Tricks
Lodash源碼
餓了么 vue-infinite-scroll
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/84048.html
摘要:事情是如何發(fā)生的最近干了件事情,發(fā)現(xiàn)了源碼的一個(gè)。樓主找到的關(guān)于和區(qū)別的資料如下關(guān)于拿來主義為什么這么多文章里會(huì)出現(xiàn)澤卡斯的錯(cuò)誤代碼樓主想到了一個(gè)詞,叫做拿來主義。的文章,就深刻抨擊了拿來主義這一現(xiàn)象。 事情是如何發(fā)生的 最近干了件事情,發(fā)現(xiàn)了 underscore 源碼的一個(gè) bug。這件事本身并沒有什么可說的,但是過程值得我們深思,記錄如下,各位看官仁者見仁智者見智。 平時(shí)有瀏覽別...
摘要:自己嘗試一下年在的文章中第一次看到的實(shí)現(xiàn)方法。這三種實(shí)現(xiàn)方法內(nèi)部不同,但是接口幾乎一致。如你所見,我們使用了參數(shù),因?yàn)槲覀冎粚?duì)用戶停止改變?yōu)g覽器大小時(shí)最后一次事件感興趣。 前幾天看到一篇文章,我的公眾號(hào)里也分享了《一次發(fā)現(xiàn)underscore源碼bug的經(jīng)歷以及對(duì)學(xué)術(shù)界拿來主義的思考》具體文章詳見,微信公眾號(hào):showImg(https://segmentfault.com/img/b...
摘要:舉例舉例通過拖拽瀏覽器窗口,可以觸發(fā)很多次事件。不支持,所以不能在服務(wù)端用于文件系統(tǒng)事件。總結(jié)將一系列迅速觸發(fā)的事件例如敲擊鍵盤合并成一個(gè)單獨(dú)的事件。確保一個(gè)持續(xù)的操作流以每毫秒執(zhí)行一次的速度執(zhí)行。 Debounce 和 Throttle 是兩個(gè)很相似但是又不同的技術(shù),都可以控制一個(gè)函數(shù)在一段時(shí)間內(nèi)執(zhí)行的次數(shù)。 當(dāng)我們?cè)诓僮?DOM 事件的時(shí)候,為函數(shù)添加 debounce 或者 th...
摘要:目的都是為了降低回調(diào)函數(shù)執(zhí)行頻率,節(jié)省計(jì)算機(jī)資源,優(yōu)化性能,提升用戶體驗(yàn)。函數(shù)防抖事件頻繁觸發(fā)的情況下,只有經(jīng)過足夠的空閑時(shí)間,才執(zhí)行代碼一次。 函數(shù)節(jié)流和函數(shù)防抖的對(duì)比分析 一、前言 前端開發(fā)中,函數(shù)節(jié)流(throttle) 和 函數(shù)防抖(debounce) 作為常用的性能優(yōu)化方法,兩者都是用于優(yōu)化高頻率執(zhí)行 js 代碼的手段,那具體它們有什么異同點(diǎn)呢?有對(duì)這兩個(gè)概念不太了解的小伙伴...
閱讀 3270·2021-11-18 10:02
閱讀 1474·2021-10-12 10:08
閱讀 1274·2021-10-11 10:58
閱讀 1289·2021-10-11 10:57
閱讀 1185·2021-10-08 10:04
閱讀 2139·2021-09-29 09:35
閱讀 790·2021-09-22 15:44
閱讀 1287·2021-09-03 10:30