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

下面再看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} 時(shí)間間隔
* @param options{Object} 如果想忽略開(kāi)始邊界上的調(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);
}
}
將以上代碼貼入瀏覽器中運(yùn)行,分三次改變窗口大小,可看到,每一次改變窗口的大小都會(huì)把開(kāi)始和結(jié)束邊界的事件處理函數(shù)各執(zhí)行一次:

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

underscore.js 的代碼實(shí)現(xiàn)
_.throttle函數(shù)
/**
* 頻率控制函數(shù), fn執(zhí)行次數(shù)不超過(guò) 1 次/delay
* @param fn{Function} 傳入的函數(shù)
* @param delay{Number} 時(shí)間間隔
* @param options{Object} 如果想忽略開(kāi)始邊界上的調(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} 時(shí)間間隔
* @param options{Object} 如果想忽略開(kāi)始邊界上的調(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
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
js獲取當(dāng)前地址 JS獲取當(dāng)前URL的示例代碼
本篇文章主要是對(duì)js獲取當(dāng)前地址 JS獲取當(dāng)前URL的示例代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-02-02
微信小程序獲取手機(jī)網(wǎng)絡(luò)狀態(tài)的方法【附源碼下載】
這篇文章主要介紹了微信小程序獲取手機(jī)網(wǎng)絡(luò)狀態(tài)的方法,涉及微信小程序wx.getNetworkType函數(shù)檢查網(wǎng)絡(luò)連接狀態(tài)的相關(guān)使用技巧,并附帶源碼供讀者下載參考,需要的朋友可以參考下2017-12-12
javascript實(shí)現(xiàn)前端成語(yǔ)點(diǎn)擊驗(yàn)證
這篇文章主要介紹了javascript實(shí)現(xiàn)前端成語(yǔ)點(diǎn)擊驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
使用JS組件實(shí)現(xiàn)帶ToolTip驗(yàn)證框的實(shí)例代碼
這篇文章主要介紹了使用JS組件實(shí)現(xiàn)帶ToolTip驗(yàn)證框的實(shí)例代碼,需要的朋友可以參考下2017-08-08
p5.js入門(mén)教程之小球動(dòng)畫(huà)示例代碼
本篇文章主要介紹了p5.js入門(mén)教程之小球動(dòng)畫(huà)示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03

