應用場景
我們經常需要監聽滾動條滾動或者鼠標的移動,但瀏覽器觸發這類事件的頻率非常高,可能在10幾毫秒就觸發一次,如果我們處理事件的函數需要操作大范圍的DOM,這對於瀏覽器的性能是個考驗,可能像chrome瀏覽器這樣優秀的瀏覽器會好一點,但放到老版本的IE下,就可能發生卡頓現象。有的時候,我們只需要處理函數執行一次,比如文本輸入驗證,執行多次處理函數反而沒有必要。
所以我們得想個辦法,減少DOM操作的頻度,也就是說稀釋處理函數的執行頻率,解決方法就是函數防抖和函數分流。函數防抖表示只執行一次處理函數,函數分流指降低處理函數的執行頻率,下面是具體解釋。
函數防抖
函數防抖指的是多次觸發事件后,事件處理函數只執行一次,而且是在事件觸發操作停止的時候。具體的思路就是延遲處理函數,如果設定的時間到來之前,又一次觸發了事件,就清除上一次的定時器,具體代碼實現如下。
var obj = document.getElementById('handle');
/**
* 事件觸發的操作
*/
function myFun() {
console.log('throttleV1');
}
/**
* 不封裝的方法
*/
obj.onmousemove = function () {
clearTimeout(myFun.timer);
myFun.timer = setTimeout(myFun,50);
}
這里有一個保存timer的技巧,如果不保存timer,那么執行完事件處理函數后,timer將被銷毀,我們也就無法再清楚定時器了,所以需要保存這個變量,即使它所在的函數作用域對應的函數已經執行完了。這里用到的技巧是把timer設置為外部函數的屬性,這樣就不會被銷毀了。
我們還可以對這個方法進行封裝。
/**
* 封裝的方法之幫頂函數
* @param method
* @param delay
* @param context
*/
function debounce(method, delay, context) {
clearTimeout(method.timer);
method.timer = setTimeout(function () {
method.call(context);
},delay);
}
obj.onmousemove = function () {
debounce(myFun,50);
};
這里涉及到了保存timer的技巧,總共有兩個方法。
- 設置為某個全局變量的屬性,例如綁定到某個全局函數上,問題是如果傳入了method是個匿名函數,綁定的timer就找不到了,所以這個方法有bug。
- 閉包 閉包的經典應用就是保留變量,下面是代碼實現。
/**
* 封裝的方法之閉包
* 閉包 如果想讓一個函數執行完后,函數內的某個變量(timer)仍舊保留,就可以使用閉包
* 把要保存的變量在父作用域聲明,其他的語句放到子作用域里,並且作為一個function返回
* 所以閉包可以理解為分離變量
*/
function debounce(method,delay) {
var timer=null;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
method.apply(context,args);
},delay);
}
}
obj.onmousemove = debounce(myFun,50);
函數分流
函數分流的思想就是計時,上面的代碼是只有在操作結束后才執行,只需要在上面的代碼上加一個計時判斷,如果超過了設定的時間,就執行一次處理函數,就達到了分流的效果。
/**
* 函數節流throttle
* @param 事件觸發的操作
* @param 延遲執行函數的時間
* @param 超過多長時間必須執行一次函數
* @returns {Function}
*/
function throttle(method, delay, mustRunDelay) {
var timer = null, args = arguments;
var start = 0, now = 0;
return function () {
var context = this;
now= Date.now();
if(!start){
start = now;
}
if(now - start >= mustRunDelay){
method.apply(context, args);
start = Date.now();
}else {
clearTimeout(timer);
timer = setTimeout(function () {
method.apply(context, args);
}, delay);
}
}
}
obj.onmousemove = throttle(myFun, 50, 500);
總結
函數防抖和函數分流的思想都是通過定時器控制函數的執行頻率。
參考目錄
http://www.alloyteam.com/2012/11/javascript-throttle/
http://web.jobbole.com/88306/
https://github.com/hanzichi/underscore-analysis/issues/21
https://github.com/hanzichi/underscore-analysis/issues/22