摘要:函數(shù)防抖和節(jié)流,都是控制事件觸發(fā)頻率的方法。封裝一個(gè)函數(shù),讓持續(xù)觸發(fā)的事件監(jiān)聽(tīng)是我們封裝的這個(gè)函數(shù),將目標(biāo)函數(shù)作為回調(diào)傳進(jìn)去,等待一段時(shí)間過(guò)后執(zhí)行目標(biāo)函數(shù)第二點(diǎn)實(shí)現(xiàn)了,再看第一點(diǎn)持續(xù)觸發(fā)不執(zhí)行。
曾經(jīng)面試時(shí)候被問(wèn)到過(guò)這個(gè),年少的我一臉無(wú)知。。。
后來(lái)工作中遇到了一個(gè)場(chǎng)景:輸入名稱(chēng)的同時(shí)去服務(wù)器校驗(yàn)名稱(chēng)是否重復(fù),但發(fā)現(xiàn)之前的代碼竟然都沒(méi)做限制,輸入一次發(fā)一次請(qǐng)求。簡(jiǎn)直忍不了,就在項(xiàng)目的utils里加上了防抖函數(shù)。
正好做一個(gè)總結(jié),加深印象。
函數(shù)防抖和節(jié)流,都是控制事件觸發(fā)頻率的方法。應(yīng)用場(chǎng)景有很多,輸入框持續(xù)輸入,將輸入內(nèi)容遠(yuǎn)程校驗(yàn)、多次觸發(fā)點(diǎn)擊事件、onScroll等等。
為了說(shuō)明問(wèn)題,假設(shè)一個(gè)場(chǎng)景:鼠標(biāo)滑過(guò)一個(gè)div,觸發(fā)onmousemove事件,它內(nèi)部的文字會(huì)顯示當(dāng)前鼠標(biāo)的坐標(biāo)。
效果是這樣的:
在上邊的場(chǎng)景下,我們不希望觸發(fā)一次就執(zhí)行一次,這就要用到防抖或節(jié)流。下面我們看一下它們能為我們做什么吧。
防抖函數(shù)防抖,這里的抖動(dòng)就是執(zhí)行的意思,而一般的抖動(dòng)都是持續(xù)的,多次的。假設(shè)函數(shù)持續(xù)多次執(zhí)行,
我們希望讓它冷靜下來(lái)再執(zhí)行。也就是當(dāng)持續(xù)觸發(fā)事件的時(shí)候,函數(shù)是完全不執(zhí)行的,等最后一次觸發(fā)結(jié)束的
一段時(shí)間之后,再去執(zhí)行。先看一下效果:
分解一下需求:
持續(xù)觸發(fā)不執(zhí)行
不觸發(fā)的一段時(shí)間之后再執(zhí)行
那么怎么實(shí)現(xiàn)上述的目標(biāo)呢?我們先看這一點(diǎn):在不觸發(fā)的一段時(shí)間之后再執(zhí)行,那就需要個(gè)定時(shí)器呀,定時(shí)器里面調(diào)用我們要執(zhí)行的函數(shù),將arguments傳入。
封裝一個(gè)函數(shù),讓持續(xù)觸發(fā)的事件監(jiān)聽(tīng)是我們封裝的這個(gè)函數(shù),將目標(biāo)函數(shù)作為回調(diào)(func)傳進(jìn)去,等待一段時(shí)間過(guò)后執(zhí)行目標(biāo)函數(shù)
function debounce(func, delay) { return function() { setTimeout(() => { func.apply(this, arguments) }, delay) } }
第二點(diǎn)實(shí)現(xiàn)了,再看第一點(diǎn):持續(xù)觸發(fā)不執(zhí)行。我們先思考一下,是什么讓我們的函數(shù)執(zhí)行了呢?是上邊的setTimeout。OK,那現(xiàn)在的問(wèn)題就變成了
持續(xù)觸發(fā),不能有setTimeout。這樣直接在事件持續(xù)觸發(fā)的時(shí)候,清掉定時(shí)器就好了。
function debounce(func, delay) { let timeout return function() { clearTimeout(timeout) // 如果持續(xù)觸發(fā),那么就清除定時(shí)器,定時(shí)器的回調(diào)就不會(huì)執(zhí)行。 timeout = setTimeout(() => { func.apply(this, arguments) }, delay) } }
用法:
box.onmousemove = debounce(function (e) { box.innerHTML = `${e.clientX}, ${e.clientY}` }, 1000)節(jié)流
節(jié)流的意思是讓函數(shù)有節(jié)制地執(zhí)行,而不是毫無(wú)節(jié)制的觸發(fā)一次就執(zhí)行一次。什么叫有節(jié)制呢?就是在一段時(shí)間內(nèi),只執(zhí)行一次。
同樣,我們分解一下:
持續(xù)觸發(fā)并不會(huì)執(zhí)行多次
到一定時(shí)間再去執(zhí)行
效果是這樣的:
思考一下,持續(xù)觸發(fā),并不會(huì)執(zhí)行,但是到時(shí)間了就會(huì)執(zhí)行。抓取一個(gè)關(guān)鍵的點(diǎn):就是執(zhí)行的時(shí)機(jī)。
要做到控制執(zhí)行的時(shí)機(jī),我們可以通過(guò)一個(gè)開(kāi)關(guān),與定時(shí)器setTimeout結(jié)合完成。
函數(shù)執(zhí)行的前提條件是開(kāi)關(guān)打開(kāi),持續(xù)觸發(fā)時(shí),持續(xù)關(guān)閉開(kāi)關(guān),等到setTimeout到時(shí)間了,再把開(kāi)關(guān)打開(kāi),函數(shù)就會(huì)執(zhí)行了。
我們看一下代碼怎么實(shí)現(xiàn):
function throttle(func, deley) { let run = true return function () { if (!run) { return // 如果開(kāi)關(guān)關(guān)閉了,那就直接不執(zhí)行下邊的代碼 } run = false // 持續(xù)觸發(fā)的話,run一直是false,就會(huì)停在上邊的判斷那里 setTimeout(() => { func.apply(this, arguments) run = true // 定時(shí)器到時(shí)間之后,會(huì)把開(kāi)關(guān)打開(kāi),我們的函數(shù)就會(huì)被執(zhí)行 }, deley) } }
調(diào)用的時(shí)候:
box.onmousemove = throttle(function (e) { box.innerHTML = `${e.clientX}, ${e.clientY}` }, 1000)
這樣,就實(shí)現(xiàn)了節(jié)流,節(jié)流還可以用時(shí)間間隔去控制,就是記錄上一次函數(shù)的執(zhí)行時(shí)間,與當(dāng)前時(shí)間作比較,如果當(dāng)前時(shí)間與上次執(zhí)行時(shí)間的時(shí)間差大于一個(gè)值,就執(zhí)行。
說(shuō)明一下節(jié)流時(shí),后面操作中應(yīng)該是因?yàn)閞un=false 所以才直接return,但是不是在return之前l(fā)et run=ture直接覆蓋掉之前的false
這里可以看一下throttle函數(shù)內(nèi)部,和函數(shù)調(diào)用的時(shí)候。首先看函數(shù)內(nèi)部,分解一下結(jié)構(gòu):
function throttle(func, deley) { return function () { // 執(zhí)行func } }
那調(diào)用時(shí)候呢?也分解一下:
throttle(function () { // 目標(biāo)函數(shù)內(nèi)容 }, 1000)
這里throttle函數(shù)執(zhí)行的結(jié)果是其內(nèi)部return的function的調(diào)用。也就是說(shuō)鼠標(biāo)經(jīng)過(guò)的事件監(jiān)聽(tīng)實(shí)際上是這個(gè)被return的function,不斷持續(xù)觸發(fā)的是它,而throttle函數(shù)只是提供了一個(gè)作用域,內(nèi)部用閉包聲明了一個(gè)run的開(kāi)關(guān)變量,由于閉包的存在,run這個(gè)變量會(huì)一直存在不被銷(xiāo)毀,而let run = true只在這個(gè)閉包(可以理解為作用域)內(nèi)只聲明了一次,但它不會(huì)被持續(xù)執(zhí)行,所以return的函數(shù)內(nèi)部的判斷不會(huì)被它覆蓋掉。根據(jù)打印結(jié)果可以看出,事實(shí)確實(shí)是如此:
總結(jié)防抖和節(jié)流巧妙地用了setTimeout,來(lái)控制函數(shù)執(zhí)行的時(shí)機(jī),優(yōu)點(diǎn)很明顯,可以節(jié)約性能,不至于多次觸發(fā)復(fù)雜的業(yè)務(wù)邏輯而造成頁(yè)面卡頓。
歡迎關(guān)注我的公眾號(hào): 一口一個(gè)前端,不定期分享我所理解的前端知識(shí)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/105490.html
摘要:首先重置防抖函數(shù)最后調(diào)用時(shí)間,然后去觸發(fā)一個(gè)定時(shí)器,保證后接下來(lái)的執(zhí)行。這就避免了手動(dòng)管理定時(shí)器。 ??之前遇到過(guò)一個(gè)場(chǎng)景,頁(yè)面上有幾個(gè)d3.js繪制的圖形。如果調(diào)整瀏覽器可視區(qū)大小,會(huì)引發(fā)圖形重繪。當(dāng)圖中的節(jié)點(diǎn)比較多的時(shí)候,頁(yè)面會(huì)顯得異??D。為了限制類(lèi)似于這種短時(shí)間內(nèi)高頻率觸發(fā)的情況,我們可以使用防抖函數(shù)。 ??實(shí)際開(kāi)發(fā)過(guò)程中,這樣的情況其實(shí)很多,比如: 頁(yè)面的scroll事件 ...
摘要:當(dāng)持續(xù)觸發(fā)事件的時(shí)候,函數(shù)是完全不執(zhí)行的,等最后一次觸發(fā)結(jié)束的一段時(shí)間之后,再去執(zhí)行原理第一次調(diào)用函數(shù)創(chuàng)建一個(gè)定時(shí)器,指定的時(shí)間間隔后運(yùn)行代碼。第二次調(diào)用函數(shù)時(shí),它會(huì)清除前一次的定時(shí)器并設(shè)置另一個(gè)。 目的:節(jié)約性能開(kāi)銷(xiāo),避免多次頻繁的觸發(fā)業(yè)務(wù)邏輯造成頁(yè)面卡頓。 應(yīng)用場(chǎng)景:節(jié)流和防抖的核心其實(shí)就是限制某一個(gè)方法被頻繁觸發(fā),比如說(shuō)DOM事件的監(jiān)聽(tīng)回調(diào),input的keyup、keydown...
摘要:運(yùn)用防抖和節(jié)流可以有效降低代碼的執(zhí)行頻率,從而解決高頻率事件的頁(yè)面卡頓問(wèn)題。在階段布局,最終確定顯示的位置和大小。在函數(shù)中,首先定義了一個(gè)空的定時(shí)器變量,用來(lái)計(jì)算時(shí)間間隔。還有一點(diǎn)要注意,在中一定要清楚定時(shí)器,不然會(huì)影響的條件判斷。 啥是節(jié)流? 節(jié)流是保證在一段時(shí)間內(nèi),代碼只執(zhí)行了一次。這個(gè)一段時(shí)間內(nèi)指的是不管用戶操作了幾次,最終僅執(zhí)行一次。比如說(shuō)一個(gè)按鈕,用戶狂點(diǎn)按鈕,但是如果用節(jié)流...
摘要:運(yùn)用防抖和節(jié)流可以有效降低代碼的執(zhí)行頻率,從而解決高頻率事件的頁(yè)面卡頓問(wèn)題。在階段布局,最終確定顯示的位置和大小。在函數(shù)中,首先定義了一個(gè)空的定時(shí)器變量,用來(lái)計(jì)算時(shí)間間隔。還有一點(diǎn)要注意,在中一定要清楚定時(shí)器,不然會(huì)影響的條件判斷。 啥是節(jié)流? 節(jié)流是保證在一段時(shí)間內(nèi),代碼只執(zhí)行了一次。這個(gè)一段時(shí)間內(nèi)指的是不管用戶操作了幾次,最終僅執(zhí)行一次。比如說(shuō)一個(gè)按鈕,用戶狂點(diǎn)按鈕,但是如果用節(jié)流...
閱讀 3061·2021-11-22 15:29
閱讀 1746·2021-10-12 10:11
閱讀 1786·2021-09-04 16:45
閱讀 2265·2021-08-25 09:39
閱讀 2804·2021-08-18 10:20
閱讀 2532·2021-08-11 11:17
閱讀 458·2019-08-30 12:49
閱讀 3325·2019-08-30 12:49