前端開發的函式防抖與函式節流
最近在開發中遇到一個問題,如果在很短的事件內連續點選同一個按鈕,按鈕的事件會觸發多次,在網上查了一下資料發現undescore.js 這個工具外掛,裡面提供了這樣兩個函式 debounce 和 throttle ;underscore1.8.2的文件是這樣的:
一、throttle(節流)
_.throttle(function, wait, [options])
option的值是 {leading:false,trailing:false}
建立並返回一個像節流閥一樣的函式,當重複呼叫函式的時候,最多每隔 wait毫秒呼叫一次該函式。對於想控制一些觸發頻率較高的事件有幫助。(愚人碼頭注:詳見:
預設情況下,throttle將在你呼叫的第一時間儘快執行這個function,並且,如果你在wait週期內呼叫任意次數的函式,都將盡快的被覆蓋。如果你想禁用第一次首先執行的話,傳遞{leading: false},還有如果你想禁用最後一次執行的話,傳遞{trailing: false}。
var throttled = _.throttle(updatePosition, 100);
$(window).scroll(throttled);
以上只是給出了用法,下面來解析一下原始碼:
// 獲取當前時間的毫秒數,當然你通過 +new Date()也可以直接獲取到當前時間毫秒數 var now = Date.now || function() { return new Date().getTime(); }; var throttle = function(func, wait, options) { var context, args, result; // 上下文this, 函式func的引數, 函式func的執行結果 var timeout = null; // 定時器 var previous = 0; // 上次事件func的執行時間毫秒數 // 可以傳入的值是 options.leading,options.trailing,都是布林型別 if (!options) options = {}; // 這是一個定時器任務函式(可以先不看這個,看完下面的程式碼再來看這個函式) var later = function() { // 如果 options.leading 為 false , 上次事件執行時間賦值為0 previous = options.leading === false ? 0 : now(); // 定時器置為null timeout = null; // 傳遞上下文引數並執行 result = func.apply(context, args); if (!timeout) context = args = null; }; // 返回一個函式 return function() { // 儲存當前上下文 context = this; // 儲存當前引數 args = arguments; var now = now(); // 當前時間毫秒數 // 更新執行func的時間previous,並禁用func第一次首先執行 if (!previous && options.leading === false) previous = now; // 還剩下多少延遲時間 var remaining = wait - (now - previous); // remaining <= 0已經足夠證明已經到達wait的時間間隔,但這裡還考慮到假如客戶端修改了系統時間則馬上執行func函式。 if (remaining <= 0 || remaining > wait) { // 如果定時器存在,清除定時器 if (timeout) { clearTimeout(timeout); timeout = null; } // 將當前時間重新賦值給previous previous = now; // 執行函式func,將結果儲存在result result = func.apply(context, args); // 如果定時器不存在就清除上下文,我認為這裡是為了防止閉包造成的記憶體洩漏 if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 沒有定時器任務,而且不禁用最後一次呼叫函式 timeout = setTimeout(later, remaining); } return result; }; };
二、debounce(防抖)
_.debounce(function, wait, [immediate])
返回 function 函式的防反跳版本, 將延遲函式的執行(真正的執行)在函式最後一次呼叫時刻的 wait 毫秒之後. 對於必須在一些輸入(多是一些使用者操作)停止到達之後執行的行為有幫助。 例如: 渲染一個Markdown格式的評論預覽, 當視窗停止改變大小之後重新計算佈局, 等等.
傳參 immediate 為 true, debounce會在 wait 時間間隔的開始呼叫這個函式 。(愚人碼頭注:並且在 waite 的時間之內,不會再次呼叫。)在類似不小心點了提交按鈕兩下而提交了兩次的情況下很有用。 (感謝
var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);
下面來解析一下原始碼:
// 獲取當前時間的毫秒數,當然你通過 +new Date()也可以直接獲取到當前時間毫秒數
var _now = Date.now || function () {
return new Date().getTime();
};
var _debounce = function (func, wait = 300, immediate) {
// 定時器,函式func的引數,上下文this, 上一個函式被返回的時間戳, 函式func的執行結果
var timeout, args, context, timestamp, result;
// 這是一個定時器任務函式(可以先不看這個,看完下面的程式碼再來看這個函式)
var later = function() {
// 該定時器時間生成的時間戳與上一函式的時間戳的差值
var last = _now() - timestamp;
// 差值在0~wait之間,就重新設定定時器,否則判斷是否已經執行過一次了,沒有執行過就執行一次該函式
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;
};
};
以上是本人的見解,如果有錯誤,還請您在評論中留言。