這周接到一個需求-給輸入框作模糊匹配。這還不簡單,監聽input事件,取到輸入值去調接口不就好了? 然然後端小哥說不行,這個接口的數據量很是大,這種方式調用接口的頻率過高,並且用戶輸入時調用根本沒有必要,只要在用戶中止輸入的那一刻切調接口就好了。 唉?這個場景聽起來怎麼這麼像防抖呢?javascript
那到底什麼是防抖呢? 你們必定見過那種左右兩邊中間放廣告位的網站,在網頁滾動時,廣告位要保持在屏幕中間,就要不斷地去計算位置,若是不作限制,在視覺上廣告位就像在「抖」。防止這種狀況,就叫防抖了!前端
防抖的原理是什麼? 我一直以爲網上流傳的例子很是形象:當咱們在乘電梯時,若是這時有人過來,咱們會出於禮貌一直按着開門按鈕等待,等到這人進電梯了,剛準備關門時,發現又有人過來了!咱們又要重複以前的操做,若是電梯空間無限大的話,咱們就要一直等待了。。。固然人的耐心是有限的!因此咱們規定了一個時間,好比10秒,若是10秒都沒人來的話,就關電梯門。java
用專業術語歸納就是:在必定時間間隔內函數被觸發屢次,但只執行最後一次。後端
最簡易版的代碼實現:性能優化
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時間間隔內,匿名函數再次被觸發,定時器都會被清除,而後從新開始計時。閉包
固然簡易版確定不能知足平常的需求,好比可能須要第一次當即執行的,因此要稍作改動:app
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。