Javascript節(jié)流函數(shù)throttle和防抖函數(shù)debounce
問題的引出
在一些場景往往由于事件頻繁被觸發(fā),因而頻繁地進行DOM操作、資源加載,導(dǎo)致UI停頓甚至瀏覽器崩潰。
在這樣的情況下,我們實際上的需求大多為停止改變大小n毫秒后執(zhí)行后續(xù)處理;而其他事件大多的需求是以一定的頻率執(zhí)行后續(xù)處理。針對這兩種需求就出現(xiàn)了debounce和throttle兩種解決辦法。
1. resize事件
2. mousemove事件
3. touchmove事件
4.scroll事件
throttle 與 debounce
在現(xiàn)在很多的javascript框架中都提供了這兩個函數(shù)。例如 jquery中有throttle和debounce插件, underscore.js ,Lodash.js 等都提供了這兩個函數(shù)。
原理:
首先我們會想到設(shè)置一定的時間范圍delay,每隔delayms 執(zhí)行不超過一次。
事件處理函數(shù)什么時候執(zhí)行能? 這里有兩個選擇,一是先執(zhí)行,再間隔delayms來等待;或者是先等待delayms,然后執(zhí)行事件處理函數(shù)。
操作過程中的事件全不管,反正只執(zhí)行一次事件處理。
相同低,這一次的事件處理可以是先執(zhí)行一次,然后后面的事件都不管; 或者前面的都不管,最后操作完了再執(zhí)行一次事件處理。
區(qū)別:
1. throttle
如果將水龍頭擰緊直到水是以水滴的形式流出,那你會發(fā)現(xiàn)每隔一段時間,就會有一滴水流出。
也就是會說預(yù)先設(shè)定一個執(zhí)行周期,當調(diào)用動作的時刻大于等于執(zhí)行周期則執(zhí)行該動作,然后進入下一個新周期。
2.debounce
如果用手指一直按住一個彈簧,它將不會彈起直到你松手為止。
也就是說當調(diào)用動作n毫秒后,才會執(zhí)行該動作,若在這n毫秒內(nèi)又調(diào)用此動作則將重新計算執(zhí)行時間。
簡單代碼實現(xiàn)及實驗結(jié)果
那么下面我們自己簡單地實現(xiàn)下這兩個函數(shù):
throttle 函數(shù):
window.addEventListener("resize", throttle(callback, 300, {leading:false}));
window.addEventListener("resize", callback2);
function callback () { console.count("Throttled"); }
function callback2 () { console.count("Not Throttled"); }
/**
* 頻率控制函數(shù), fn執(zhí)行次數(shù)不超過 1 次/delay
* @param fn{Function} 傳入的函數(shù)
* @param delay{Number} 時間間隔
* @param options{Object} 如果想忽略開始邊界上的調(diào)用則傳入 {leading:false},
* 如果想忽略結(jié)束邊界上的調(diào)用則傳入 {trailing:false},
* @returns {Function} 返回調(diào)用函數(shù)
*/
function throttle(fn,delay,options) {
var wait=false;
if (!options) options = {};
return function(){
var that = this,args=arguments;
if(!wait){
if (!(options.leading === false)){
fn.apply(that,args);
}
wait=true;
setTimeout(function () {
if (!(options.trailing === false)){
fn.apply(that,args);
}
wait=false;
},delay);
}
}
}
將以上代碼貼入瀏覽器中運行,可得到:

下面再看debounce函數(shù)的情況,
debounce 函數(shù):
window.addEventListener("resize", throttle(callback, 300, {leading:false}));
window.addEventListener("resize", callback2);
function callback () { console.count("Throttled"); }
function callback2 () { console.count("Not Throttled"); }
/**
* 空閑控制函數(shù), fn僅執(zhí)行一次
* @param fn{Function} 傳入的函數(shù)
* @param delay{Number} 時間間隔
* @param options{Object} 如果想忽略開始邊界上的調(diào)用則傳入 {leading:false},
* 如果想忽略結(jié)束邊界上的調(diào)用則傳入 {trailing:false},
* @returns {Function} 返回調(diào)用函數(shù)
*/
function debounce(fn, delay, options) {
var timeoutId;
if (!options) options = {};
var leadingExc = false;
return function() {
var that = this,
args = arguments;
if (!leadingExc&&!(options.leading === false)) {
fn.apply(that, args);
}
leadingExc=true;
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(function() {
if (!(options.trailing === false)) {
fn.apply(that, args);
}
leadingExc=false;
}, delay);
}
}
將以上代碼貼入瀏覽器中運行,分三次改變窗口大小,可看到,每一次改變窗口的大小都會把開始和結(jié)束邊界的事件處理函數(shù)各執(zhí)行一次:

如果是一次性改變窗口大小,會發(fā)現(xiàn)開始和結(jié)束的邊界各執(zhí)行一次時間處理函數(shù),請注意與一次性改變窗口大小時 throttle 情況的對比:

underscore.js 的代碼實現(xiàn)
_.throttle函數(shù)
/**
* 頻率控制函數(shù), fn執(zhí)行次數(shù)不超過 1 次/delay
* @param fn{Function} 傳入的函數(shù)
* @param delay{Number} 時間間隔
* @param options{Object} 如果想忽略開始邊界上的調(diào)用則傳入 {leading:false},
* 如果想忽略結(jié)束邊界上的調(diào)用則傳入 {trailing:false},
* @returns {Function} 返回調(diào)用函數(shù)
*/
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
var now = _.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
_.debounce函數(shù)
/**
* 空閑控制函數(shù), fn僅執(zhí)行一次
* @param fn{Function} 傳入的函數(shù)
* @param delay{Number} 時間間隔
* @param options{Object} 如果想忽略開始邊界上的調(diào)用則傳入 {leading:false},
* 如果想忽略結(jié)束邊界上的調(diào)用則傳入 {trailing:false},
* @returns {Function} 返回調(diào)用函數(shù)
*/
_.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
var last = _.now() - timestamp;
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
timestamp = _.now();
var callNow = immediate && !timeout;
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
};
參考的文章
Debounce and Throttle: a visual explanation
jQuery throttle / debounce: Sometimes, less is more!
underscore.js
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
微信小程序獲取手機網(wǎng)絡(luò)狀態(tài)的方法【附源碼下載】
這篇文章主要介紹了微信小程序獲取手機網(wǎng)絡(luò)狀態(tài)的方法,涉及微信小程序wx.getNetworkType函數(shù)檢查網(wǎng)絡(luò)連接狀態(tài)的相關(guān)使用技巧,并附帶源碼供讀者下載參考,需要的朋友可以參考下2017-12-12
使用JS組件實現(xiàn)帶ToolTip驗證框的實例代碼
這篇文章主要介紹了使用JS組件實現(xiàn)帶ToolTip驗證框的實例代碼,需要的朋友可以參考下2017-08-08

