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