前端性能优化之防抖-debounce
這周接到一個需求-給輸入框做模糊匹配。這還不簡單,監(jiān)聽input事件,取到輸入值去調(diào)接口不就行了? 然而后端小哥說不行,這個接口的數(shù)據(jù)量非常大,這種方式調(diào)用接口的頻率太高,而且用戶輸入時調(diào)用根本沒有必要,只要在用戶停止輸入的那一刻切調(diào)接口就行了。 唉?這個場景聽起來怎么這么像防抖呢?
那到底什么是防抖呢? 大家一定見過那種左右兩邊中間放廣告位的網(wǎng)站,在網(wǎng)頁滾動時,廣告位要保持在屏幕中間,就要不斷地去計算位置,如果不做限制,在視覺上廣告位就像在“抖”。防止這種情況,就叫防抖了!
防抖的原理是什么? 我一直覺得網(wǎng)上流傳的例子非常形象:當(dāng)我們在乘電梯時,如果這時有人過來,我們會出于禮貌一直按著開門按鈕等待,等到這人進(jìn)電梯了,剛準(zhǔn)備關(guān)門時,發(fā)現(xiàn)又有人過來了!我們又要重復(fù)之前的操作,如果電梯空間無限大的話,我們就要一直等待了。。。當(dāng)然人的耐心是有限的!所以我們規(guī)定了一個時間,比如10秒,如果10秒都沒人來的話,就關(guān)電梯門。
用專業(yè)術(shù)語概括就是:在一定時間間隔內(nèi)函數(shù)被觸發(fā)多次,但只執(zhí)行最后一次。
最簡易版的代碼實現(xiàn):
function debounce(fn, delay) {let timer = null;return function() {const context = this;const args = arguments;if (timer) {clearTimeout(timer);timer = null;}timer = setTimeout(() => {fn.apply(context, args);}, delay);}; } 復(fù)制代碼fn是要進(jìn)行防抖的函數(shù),delay是設(shè)定的延時,debounce返回一個匿名函數(shù),形成閉包,內(nèi)部維護(hù)了一個私有變量timer。我們一直會觸發(fā)的是這個返回的匿名函數(shù),定時器會返回一個Id值賦給timer,如果在delay時間間隔內(nèi),匿名函數(shù)再次被觸發(fā),定時器都會被清除,然后重新開始計時。
當(dāng)然簡易版肯定不能滿足日常的需求,比如可能需要第一次立即執(zhí)行的,所以要稍做改動:
function debounce(fn, delay, immediate) {let timer = null;return function() {const context = this;const args = arguments;timer && clearTimeout(timer);if(immediate) {const doNow = !timer;timer = setTimeout(() => {timer = null;}, delay);doNow && fn.apply(context, args);}else {timer = setTimeout(() => {fn.apply(context, args);}, delay);}}; } 復(fù)制代碼比起簡易版,多了個參數(shù)immediate來區(qū)分是否需要立即執(zhí)行。其它與簡易版幾乎一致的邏輯,除了判斷立即執(zhí)行的地方:
const doNow = !timer;timer = setTimeout(() => {timer = null; }, delay);doNow && fn.apply(context, args); 復(fù)制代碼doNow變量的值為!timer,只有!timer為true的情況下,才會執(zhí)行fn函數(shù)。第一次執(zhí)行時,timer的初始值為null,所以會立即執(zhí)行fn。接下來非第一次執(zhí)行的情況下,等待delay時間后才能再次觸發(fā)執(zhí)行fn。 注意!與簡易版的區(qū)別,簡易版是一定時間多次內(nèi)觸發(fā),執(zhí)行最后一次。而立即執(zhí)行版是不會執(zhí)行最后一次的,需要再次觸發(fā)。
防抖的函數(shù)可能是有返回值,我們也要做兼容:
function debounce(fn, delay, immediate) {let timer = null;return function() {const context = this;const args = arguments;let result = undefined;timer && clearTimeout(timer);if (immediate) {const doNow = !timer;timer = setTimeout(() => {timer = null;}, delay);if (doNow) {result = fn.apply(context, args);} }else {timer = setTimeout(() => {fn.apply(context, args);}, delay);}return result;}; } 復(fù)制代碼但是這個實現(xiàn)方式有個缺點,因為除了第一次立即執(zhí)行,其它情況都是在定時器中執(zhí)行的,也就是異步執(zhí)行,返回值會是undefined。
考慮到異步,我們也可以返回Promise:
function debounce(fn, delay, immediate) {let timer = null;return function() {const context = this;const args = arguments;return new Promise((resolve, reject) => {timer && clearTimeout(timer);if (immediate) {const doNow = !timer;timer = setTimeout(() => {timer = null;}, delay);doNow && resolve(fn.apply(context, args));}else {timer = setTimeout(() => {resolve(fn.apply(context, args));}, delay);}});}; } 復(fù)制代碼如此,只要fn被執(zhí)行,那必定可以拿到返回值!這也是防抖的終極版了!
下次聊聊防抖的兄弟-前端性能優(yōu)化之節(jié)流-throttle。
總結(jié)
以上是生活随笔為你收集整理的前端性能优化之防抖-debounce的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Netty3之ServerBootstr
- 下一篇: android中的相对路径