摘要:防抖和節(jié)流嚴(yán)格算起來(lái)應(yīng)該屬于性能優(yōu)化的知識(shí),但實(shí)際上遇到的頻率相當(dāng)高,處理不當(dāng)或者放任不管就容易引起瀏覽器卡死。
防抖和節(jié)流嚴(yán)格算起來(lái)應(yīng)該屬于性能優(yōu)化的知識(shí),但實(shí)際上遇到的頻率相當(dāng)高,處理不當(dāng)或者放任不管就容易引起瀏覽器卡死。所以還是很有必要早點(diǎn)掌握的。(信我,你看完肯定就懂了)
從滾動(dòng)條監(jiān)聽(tīng)的例子說(shuō)起先說(shuō)一個(gè)常見(jiàn)的功能,很多網(wǎng)站會(huì)提供這么一個(gè)按鈕:用于返回頂部。
這個(gè)按鈕只會(huì)在滾動(dòng)到距離頂部一定位置之后才出現(xiàn),那么我們現(xiàn)在抽象出這個(gè)功能需求-- 監(jiān)聽(tīng)瀏覽器滾動(dòng)事件,返回當(dāng)前滾條與頂部的距離
這個(gè)需求很簡(jiǎn)單,直接寫:
function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log("滾動(dòng)條位置:" + scrollTop); } window.onscroll = showTop
但是!
在運(yùn)行的時(shí)候會(huì)發(fā)現(xiàn)存在一個(gè)問(wèn)題:這個(gè)函數(shù)的默認(rèn)執(zhí)行頻率,太!高!了!。 高到什么程度呢?以chrome為例,我們可以點(diǎn)擊選中一個(gè)頁(yè)面的滾動(dòng)條,然后點(diǎn)擊一次鍵盤的【向下方向鍵】,會(huì)發(fā)現(xiàn)函數(shù)執(zhí)行了8-9次!
然而實(shí)際上我們并不需要如此高頻的反饋,畢竟瀏覽器的性能是有限的,不應(yīng)該浪費(fèi)在這里,所以接著討論如何優(yōu)化這種場(chǎng)景。
防抖(debounce)基于上述場(chǎng)景,首先提出第一種思路:在第一次觸發(fā)事件時(shí),不立即執(zhí)行函數(shù),而是給出一個(gè)期限值比如200ms,然后:
如果在200ms內(nèi)沒(méi)有再次觸發(fā)滾動(dòng)事件,那么就執(zhí)行函數(shù)
如果在200ms內(nèi)再次觸發(fā)滾動(dòng)事件,那么當(dāng)前的計(jì)時(shí)取消,重新開(kāi)始計(jì)時(shí)
效果:如果短時(shí)間內(nèi)大量觸發(fā)同一事件,只會(huì)執(zhí)行一次函數(shù)。
實(shí)現(xiàn):既然前面都提到了計(jì)時(shí),那實(shí)現(xiàn)的關(guān)鍵就在于setTimeOut這個(gè)函數(shù),由于還需要一個(gè)變量來(lái)保存計(jì)時(shí),考慮維護(hù)全局純凈,可以借助閉包來(lái)實(shí)現(xiàn):
/* * fn [function] 需要防抖的函數(shù) * delay [number] 毫秒,防抖期限值 */ function debounce(fn,delay){ let timer = null //借助閉包 return function() { if(timer){ clearTimeout(timer) //進(jìn)入該分支語(yǔ)句,說(shuō)明當(dāng)前正在一個(gè)計(jì)時(shí)過(guò)程中,并且又觸發(fā)了相同事件。所以要取消當(dāng)前的計(jì)時(shí),重新開(kāi)始計(jì)時(shí) timer = setTimeOut(fn,delay) }else{ timer = setTimeOut(fn,delay) // 進(jìn)入該分支說(shuō)明當(dāng)前并沒(méi)有在計(jì)時(shí),那么就開(kāi)始一個(gè)計(jì)時(shí) } } }
當(dāng)然 上述代碼是為了貼合思路,方便理解(這么貼心不給個(gè)贊咩?),寫完會(huì)發(fā)現(xiàn)其實(shí) time = setTimeOut(fn,delay)是一定會(huì)執(zhí)行的,所以可以稍微簡(jiǎn)化下:
/*****************************簡(jiǎn)化后的分割線 ******************************/ function debounce(fn,delay){ let timer = null //借助閉包 return function() { if(timer){ clearTimeout(timer) } timer = setTimeout(fn,delay) // 簡(jiǎn)化寫法 } } // 然后是舊代碼 function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log("滾動(dòng)條位置:" + scrollTop); } window.onscroll = debounce(showTop,1000) // 為了方便觀察效果我們?nèi)€(gè)大點(diǎn)的間斷值,實(shí)際使用根據(jù)需要來(lái)配置
此時(shí)會(huì)發(fā)現(xiàn),必須在停止?jié)L動(dòng)1秒以后,才會(huì)打印出滾動(dòng)條位置。
到這里,已經(jīng)把防抖實(shí)現(xiàn)了,現(xiàn)在給出定義:
對(duì)于短時(shí)間內(nèi)連續(xù)觸發(fā)的事件(上面的滾動(dòng)事件),防抖的含義就是讓某個(gè)時(shí)間期限(如上面的1000毫秒)內(nèi),事件處理函數(shù)只執(zhí)行一次。
節(jié)流(throttle)繼續(xù)思考,使用上面的防抖方案來(lái)處理問(wèn)題的結(jié)果是:
如果在限定時(shí)間段內(nèi),不斷觸發(fā)滾動(dòng)事件(比如某個(gè)用戶閑著無(wú)聊,按住滾動(dòng)不斷的拖來(lái)拖去),只要不停止觸發(fā),理論上就永遠(yuǎn)不會(huì)輸出當(dāng)前距離頂部的距離。
但是如果產(chǎn)品同學(xué)的期望處理方案是:即使用戶不斷拖動(dòng)滾動(dòng)條,也能在某個(gè)時(shí)間間隔之后給出反饋呢?(此處暫且不論哪種方案更合適,既然產(chǎn)品爸爸說(shuō)話了我們就先考慮怎么實(shí)現(xiàn))
其實(shí)很簡(jiǎn)單:我們可以設(shè)計(jì)一種類似控制閥門一樣定期開(kāi)放的函數(shù),也就是讓函數(shù)執(zhí)行一次后,在某個(gè)時(shí)間段內(nèi)暫時(shí)失效,過(guò)了這段時(shí)間后再重新激活(類似于技能冷卻時(shí)間)。
效果:如果短時(shí)間內(nèi)大量觸發(fā)同一事件,那么在函數(shù)執(zhí)行一次之后,該函數(shù)在指定的時(shí)間期限內(nèi)不再工作,直至過(guò)了這段時(shí)間才重新生效。
實(shí)現(xiàn) 這里借助setTimeout來(lái)做一個(gè)簡(jiǎn)單的實(shí)現(xiàn),加上一個(gè)狀態(tài)位valid來(lái)表示當(dāng)前函數(shù)是否處于工作狀態(tài):
function throttle(fn,delay){ let valid = true return function() { if(!valid){ //休息時(shí)間 暫不接客 return false } // 工作時(shí)間,執(zhí)行函數(shù)并且在間隔期內(nèi)把狀態(tài)位設(shè)為無(wú)效 valid = false setTimeout(() => { fn() valid = true; }, delay) } } /* 請(qǐng)注意,節(jié)流函數(shù)并不止上面這種實(shí)現(xiàn)方案, 例如可以完全不借助setTimeout,可以把狀態(tài)位換成時(shí)間戳,然后利用時(shí)間戳差值是否大于指定間隔時(shí)間來(lái)做判定。 也可以直接將setTimeout的返回的標(biāo)記當(dāng)做判斷條件-判斷當(dāng)前定時(shí)器是否存在,如果存在表示還在冷卻,并且在執(zhí)行fn之后消除定時(shí)器表示激活,原理都一樣 */ // 以下照舊 function showTop () { var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; console.log("滾動(dòng)條位置:" + scrollTop); } window.onscroll = throttle(showTop,1000)
運(yùn)行以上代碼的結(jié)果是:
如果一直拖著滾動(dòng)條進(jìn)行滾動(dòng),那么會(huì)以1s的時(shí)間間隔,持續(xù)輸出當(dāng)前位置和頂部的距離
其他應(yīng)用場(chǎng)景舉例講完了這兩個(gè)技巧,下面介紹一下平時(shí)開(kāi)發(fā)中常遇到的場(chǎng)景:
搜索框input事件,例如要支持輸入實(shí)時(shí)搜索可以使用節(jié)流方案(間隔一段時(shí)間就必須查詢相關(guān)內(nèi)容),或者實(shí)現(xiàn)輸入間隔大于某個(gè)值(如500ms),就當(dāng)做用戶輸入完成,然后開(kāi)始搜索,具體使用哪種方案要看業(yè)務(wù)需求。
頁(yè)面resize事件,常見(jiàn)于需要做頁(yè)面適配的時(shí)候。需要根據(jù)最終呈現(xiàn)的頁(yè)面情況進(jìn)行dom渲染(這種情形一般是使用防抖,因?yàn)橹恍枰袛嘧詈笠淮蔚淖兓闆r)
思考總結(jié)上述內(nèi)容基于防抖和節(jié)流的核心思路設(shè)計(jì)了簡(jiǎn)單的實(shí)現(xiàn)算法,但是不代表實(shí)際的庫(kù)(例如undercore js)的源碼就直接是這樣的,最起碼的可以看出,在上述代碼實(shí)現(xiàn)中,因?yàn)?b>showTop本身的很簡(jiǎn)單,無(wú)需考慮作用域和參數(shù)傳遞,所以連apply都沒(méi)有用到,實(shí)際上肯定還要考慮傳遞argument以及上下文環(huán)境(畢竟apply需要用到this對(duì)象)。這里的相關(guān)知識(shí)在本專欄《柯里化》和《this對(duì)象》的文章里也有提到。本文依然堅(jiān)持突出核心代碼,盡可能剝離無(wú)關(guān)功能點(diǎn)的思路行文因此不做贅述。
慣例:如果內(nèi)容有錯(cuò)誤的地方歡迎指出(覺(jué)得看著不理解不舒服想吐槽也完全沒(méi)問(wèn)題);如果有幫助,歡迎點(diǎn)贊和收藏,轉(zhuǎn)載請(qǐng)征得同意后著明出處,如果有問(wèn)題也歡迎私信交流,主頁(yè)有郵箱地址
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/108989.html
摘要:隆重請(qǐng)出主角防抖與節(jié)流。防抖與節(jié)流的異同相同都是防止某一時(shí)間段內(nèi),函數(shù)被頻繁調(diào)用執(zhí)行,通過(guò)時(shí)間頻率控制,減少回調(diào)函數(shù)執(zhí)行次數(shù),來(lái)實(shí)現(xiàn)相關(guān)性能優(yōu)化。參考文章分鐘理解的節(jié)流防抖及使用場(chǎng)景函數(shù)防抖和節(jié)流 showImg(https://segmentfault.com/img/bVburM8?w=800&h=600); 本篇課題,或許早已是爛大街的解讀文章。不過(guò)春招系列面試下來(lái),不少伙伴們還...
摘要:本文會(huì)分別介紹什么是防抖和節(jié)流,它們的應(yīng)用場(chǎng)景,和實(shí)現(xiàn)方式。防抖和節(jié)流都是為了解決短時(shí)間內(nèi)大量觸發(fā)某函數(shù)而導(dǎo)致的性能問(wèn)題,比如觸發(fā)頻率過(guò)高導(dǎo)致的響應(yīng)速度跟不上觸發(fā)頻率,出現(xiàn)延遲,假死或卡頓的現(xiàn)象。 本文由小芭樂(lè)發(fā)表 0. 引入 首先舉一個(gè)例子: 模擬在輸入框輸入后做ajax查詢請(qǐng)求,沒(méi)有加入防抖和節(jié)流的效果,這里附上完整可執(zhí)行代碼: 沒(méi)有防抖 ...
摘要:若時(shí)間差大于間隔時(shí)間,則立刻執(zhí)行一次函數(shù)。不同點(diǎn)函數(shù)防抖,在一段連續(xù)操作結(jié)束后,處理回調(diào),利用和實(shí)現(xiàn)。函數(shù)防抖關(guān)注一定時(shí)間連續(xù)觸發(fā)的事件只在最后執(zhí)行一次,而函數(shù)節(jié)流側(cè)重于一段時(shí)間內(nèi)只執(zhí)行一次。 原博客地址,歡迎star 函數(shù)防抖和節(jié)流 函數(shù)防抖和函數(shù)節(jié)流:優(yōu)化高頻率執(zhí)行js代碼的一種手段,js中的一些事件如瀏覽器的resize、scroll,鼠標(biāo)的mousemove、mouseover...
摘要:應(yīng)用場(chǎng)景給按鈕加函數(shù)防抖防止表單多次提交。對(duì)于輸入框連續(xù)輸入進(jìn)行驗(yàn)證時(shí),用函數(shù)防抖能有效減少請(qǐng)求次數(shù)。參考十分鐘學(xué)會(huì)防抖和節(jié)流輕松理解函數(shù)節(jié)流和函數(shù)防抖 函數(shù)防抖和節(jié)流 防抖 對(duì)于觸發(fā)非常頻繁又沒(méi)有必要每次都執(zhí)行的事件,希望合并到一次去執(zhí)行; 實(shí)現(xiàn)思路: 事件觸發(fā)后,在規(guī)定的時(shí)間范圍內(nèi)如果事件重復(fù)觸發(fā),那么忽略之前觸發(fā)的事件,并且重新開(kāi)始計(jì)時(shí),直到某一次事件觸發(fā)后大于規(guī)定時(shí)間,我們才執(zhí)...
摘要:概念函數(shù)防抖和函數(shù)節(jié)流,兩者都是優(yōu)化高頻率執(zhí)行代碼的一種手段。防抖任務(wù)頻繁觸發(fā)的情況下,只有任務(wù)觸發(fā)的間隔超過(guò)指定間隔的時(shí)候,任務(wù)才會(huì)執(zhí)行。節(jié)流指定時(shí)間間隔內(nèi)只會(huì)執(zhí)行一次任務(wù)一定時(shí)間內(nèi)方法只跑一次。 概念 函數(shù)防抖和函數(shù)節(jié)流,兩者都是優(yōu)化高頻率執(zhí)行js代碼的一種手段。 防抖:任務(wù)頻繁觸發(fā)的情況下,只有任務(wù)觸發(fā)的間隔超過(guò)指定間隔的時(shí)候,任務(wù)才會(huì)執(zhí)行。 節(jié)流:指定時(shí)間間隔內(nèi)只會(huì)執(zhí)行一次任...
閱讀 1940·2021-10-11 10:59
閱讀 1046·2021-09-07 09:59
閱讀 2244·2021-08-27 16:17
閱讀 2793·2019-08-30 15:54
閱讀 2284·2019-08-30 12:58
閱讀 1786·2019-08-30 12:53
閱讀 1479·2019-08-28 18:13
閱讀 739·2019-08-26 13:35