前言
為什麼我們要在專案中使用防抖???
隨著計算機的不斷髮展,瀏覽器也在飛速的更新迭代。從剛開始的只是展示圖文排版的網頁,到現在人機互動和科學計算的動態專案。不同的時代同樣也推動著思想的發展,到目前為止的網頁,直面使用者最多的也就是網站的視覺特效和互動效能。效能提升更是作為前端工程師都要關注的一個問題。防抖作為程式碼層面的提升效能的工具是很有必要學一下的。
防抖能做的事
在前端開發中會遇到一些頻繁的事件觸發,比如
- window的resize、scroll事件等
- mousedown、mousemove事件
- 搜尋框的input事件等
上面這些事件頻繁的觸發會引起頁面的抖動,如果涉及到請求,同樣也會發生請求爆炸。在前端社會需要的有人來站出來拯救這個戰亂的時候,防抖來了。
防抖的實現原理
你儘管觸發事件,但是我一定在事件觸發n秒後才執行,如果你在一個事件觸發的n秒內又觸發了這個事件,那麼我就以新的事件的時間為準,n秒後再執行。總之,就是要等這個事件觸發完n秒後再觸發事件。
underscore防抖的實現
防抖就不一步一步的實現了,有需求的話請欣賞冴羽大大的 跟著underscore學防抖文章,每一步的實現講的非常清晰,同時也感謝冴羽大大讓我又明白了一項技術。
實現防抖的注意點。
- 由於是事件處理,debounce函式必須要接受一個函式作為引數,並返回一個函式,第二個引數為觸發事件所等待的時間。其次,既然等待就必須要有定時器,當每次觸發事件的時候首先要清除定時器。
- 當繫結時間處理的時候,此時的this應該是事件源本身。
- event事件物件:當我們觸發事件的時候,相應的我們會得到event事件物件。
- 立即執行,有時候我們想當我們首次觸發事件的時候會立即執行,以後連續的操作會出現防抖操作。立即執行我們的防抖函式要加上第三個引數判斷是否要立即執行。
- 返回值:如果我們的函式中是有返回值的,當immediate為false的時候,因為使用了setTimeout,最後return的時候一直是undefined,所以,只在immediate為true的時候返回值。
- 取消:有時候我們需要這樣的一個需求,當我的防抖時間間隔很長的時候。在這個漫長的等待過程中,我想要取消防抖。再次執行然後觸發。
根據上面這些實現防抖的注意點,我們的underscore防抖誕生了。
function debounce(fn,time,immediate) {
var timeout,result;
var debounced = function (){
var context = this;
var args = Array.prototype.slice.call(arguments);
if(timeout){
clearTimeout(timeout);
}
if(immediate){
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
},time)
if(callNow){
result = fn.apply(context,args);
}
} else{
timeout = setTimeout(function(){
fn.apply(context,args);
},time)
}
return result;
}
debounced.cancel = function(){
clearTimeout(timeout);
timeout = null;
}
return debounced;
}
複製程式碼
說說我碰到的問題
立即執行
underscore防抖的立即執行的功能很簡單的可以看出避免使用者等候的時間太長,所做的優化,但是會發現當頻繁觸發事件的時候,立即執行了一次,後續就不會觸發第二次。在滑鼠移動事件的可以很完美,但是到搜尋框上就不那麼美妙了。
右邊的數字是顯示input觸發的次數,這就可以看出,我按了那麼多,它只執行了開始的一次,最後的一次並沒有執行。
個人觀點
在立即執行這個功能上,存在著不同需求的原因,根據不同的需求去選擇合適的功能。比如在滑鼠划動上可以新增上立即觸發功能而放棄最後一次的觸發,在搜尋框上面則不需要立即觸發功能,用最後一次的觸發來實現防抖功能。
取消
取消,優化了使用者等待時間過長這樣一個問題,但同樣也會出現一些小問題,這個問題出現在不是立即執行上面,就上面的input輸入框而言,當我在漫長的等待過程中,點選了取消,這個事件就永遠不會觸發,有可以我們想要的結果是當我們點選了取消,事件直接觸發了,多好。
因為上面這個需求,我在underscore上面改動了一點點,達到了我想要的目的。
寫出自己underscore防抖
跟緊時代的潮流,我自己寫的underscore防抖是以ES6的calss寫的,同樣也是映著模組化開發的思想。話不多說,先把程式碼放上。
class Debounce {
constructor(fn,time = 1000,immediate = false){
if(typeof fn !== "function"){
throw new Error("first param is not function");
}
this.fn = fn;
this.time = time;
this.immediate = immediate;
this.context;
this.args;
this.timeout;
this.result;
}
debounced(){
let _self = this;
return function(...args) {
_self.context = this;
_self.args = args;
if (_self.timeout) {
clearTimeout(_self.timeout);
}
if (_self.immediate) {
let callNow = !_self.timeout;
_self.timeout = setTimeout(() => {
_self.timeout = null;
},_self.time);
if(callNow) {
_self.result = _self.fn.apply(_self.context, _self.args);
}
} else {
_self.timeout = setTimeout( () => {
_self.fn.apply(_self.context,_self.args);
},_self.time)
}
return _self.result;
}
}
cancel(){
if(!this.immediate) {
this.fn.apply(this.context,this.args);
}
clearTimeout(this.timeout);
this.timeout = null;
}
}
複製程式碼
使用方法
var box = document.getElementById("box");
var input = document.getElementById("input");
var span = document.getElementById("span");
function func(e) {
console.log(e);
console.log(this);
box.innerHTML = count++;
return 123;
};
function test(e) {
console.log(e);
span.innerHTML = count++;
}
//在這裡在這裡
const inputDebounce = new Debounce(test,10000);
const mouseDebounce = new Debounce(func,10000,true);
input.oninput = inputDebounce.debounced();
box.onmousemove = mouseDebounce.debounced();
複製程式碼
class中的constructor方法引數採用引數預設值的形式,把time和immediate寫成預設值,並在程式碼中對第一個引數是不是function做了檢測。然後把所有的所要用到的變數值暴露到了例項屬性上,underscore防抖是事件函式的this和arguments都在內部。 暴露出來的目的就是為了實現取消功能。在cancel方法中判斷如果當前不是立即執行的行為,當觸發取消事件的時候就必須立即執行事件函式。
以input為例,程式碼如下
document.getElementById("button").addEventListener('click', function () {
inputDebounce.cancel();
})
複製程式碼
把等待時間改為10000試驗一下。
成功了,此時不是立即執行狀態,在漫長的等待中當我點選close按鈕的時候,1出現了,開心。