1. 什么是節流
節流就是對於連續多次觸發事件,事件只在規定時間間隔到了才執行
可以想象成一個沙漏,頂部有很多沙子,但是流下來的沙子卻只有一點點,起到了一個限制的作用,不至於全部沙子一哄而下。
在實際應用中,可以用在:
- 鼠標點擊事件:鼠標不斷點擊,但回調函數只會在規定的時間到時才會運行
- 監聽滾動事件:例如滑到底部自動加載更多,就可以用到節流限制不是每一次滾動都執行,而是滾動時每隔一段時間去執行
- input輸入:input輸入進行搜索請求時,也可以使用到節流,每隔一段時間再去發送請求
- ……
節流與防抖的區別
在[JavaScript] 手寫實現一個防抖函數(Debounce)一文中介紹了防抖,讀者可能會覺得節流與防抖有點像,其實仔細斟酌就能發現他們的不同
節流是指對於連續觸發的事件,每隔一段固定時間執行一次,只要事件持續出發就可以執行很多次。(在節流里涉及的時間主要是指事件執行的間隔時間)
防抖則是對連續觸發的事件,只會執行一次,不管事件觸發多少次,都只執行一次。(在防抖里設置的時間可以說是對連續觸發時間的定義,在設置時間內運行的事件就被稱為連續觸發的事件)
2. 節流的實現(時間戳版)
節流的實現可以利用時間戳,使用這一方法,會立即執行
const throttle = (func, wait) => {
// 初始化事件開始的時間為0
let preTime = 0;
return function() {
// 下面兩行不懂的可以看看防抖實現的那篇文章
let context = this;
let args = arguments;
// 獲取當前的時間,使用+來轉化為數字類型,方便后面做減法
let now = +new Date();
// 當前時間減去之前的時間,結果大於設定的間隔時間才會執行函數
if (now - preTime > wait) {
func.apply(context, args);
preTime = now;
}
}
}
3.節流的實現(定時器版)
節流還可以使用定時器實現,使用這一方法,不會立即執行,但會在最后一次停止觸發后再執行一次
const throttle2 = (func, wait) => {
let timeout;
return function() {
let context = this;
let args = arguments;
// 若沒有定時器,說明上一次設定的定時器已到時銷毀
if (!timeout) {
timeout = setTimeout(function() {
func.apply(context, args);
timeout = null;
}, wait)
}
}
}
4. 節流的實現(組合版)
通過前兩種方法,可以發現他們在觸發的時機上有所區別,我們可不可以將他們結合起來,當然可以!
function throttle3(func, wait){
let context, args, timeout;
let pretime = 0;
let later = function(){
pretime = +new Date();
timeout = null;
func.apply(context, args);
};
let throttled = function(){
context = this;
args = arguments;
var now = +new Date();
var remaining = wait - (now - pretime);
// 剩余時間為負數表示下一次執行需要立即執行
// remaining > wait在修改了系統時間的情況下可能發生
if(remaining <= 0 || remaining > wait){
// 如果有設置過定時器,清空並置為null
if(timeout){
clearTimeout(timeout)
timeout = null;
}
pretime = now;
func.apply(context,args);
}else if(!timeout){
// 需要在剩余時間后執行
timeout = setTimeout(later,remaining);
}
};
return throttled;
}
5. 節流的實現(自定義版)
在組合版中,實現了可以立即執行,停止觸發后再執行一次的效果,但是有時候我們想手動得控制是要立即執行還是停止觸發后再執行一次,或者兩種效果都要,或者兩種效果都不要,讓我們來實現一下吧
在組合版的基礎上,設置一個options對象參數,根據傳的值判斷要哪一種效果,規定:
leading:false
表示禁用第一次執行
trailing: false
表示禁用停止觸發的回調
function throttle4(func, wait, options) {
let timeout, context, args, result;
let pretime = 0;
// options參數是可選的
if (!options) options = {};
let later = function() {
pretime = options.leading === false ? 0 : +new Date();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
}
let throttled = function() {
context = this;
args = arguments;
let now = +new Date();
// 如果禁用第一次執行,那么將上一次執行的時間於當前時間相等即可
if (!pretime && options.leading === false) pretime = now;
let remaining = wait - (now - pretime);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
pretime = now;
func.apply(context, args);
if (!timeout) context = args = null;
// 允許停止觸發后執行回調函數,只有當traling為true時才會執行下面的代碼
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};
return throttled
}
// 測試
container.onmousemove = throttle4(getUserAction,2000, {leading: false, trailing: true});
讓我來詳細解釋一下
- 使用時間戳的方法,是可以達到第一次觸發馬上執行的效果,使用定時器的方法,可以達到停止觸發后再執行一次的效果。
- 當設置了
leading:true
,trailing: false
允許第一次執行時,且不允許停止觸發后再執行一次,當前時間pretime的初始值為0,接下來就一直使用時間戳的方法進行節流。 - 當設置了
leading:true
,trailing: true
允許第一次執行,且允許停止觸發后再執行一次,那么會在間隔時間大於等於wait的情況下,使用時間戳進行節流(達到第一次執行的效果),在間隔時間小於wait去觸發時,設置定時器進行節流 (達到停止觸發后再執行一次的效果),這就和我們的組合版相同。 - 當設置了
leading:false
,trailing: true
禁止第一次執行,允許停止觸發后再執行一次,當前時間的初始值為now(達到禁止第一次執行的效果),在使用定時器方法時,將pretime設置為0(pretime為0是判斷是否為第一次執行的條件,如果同時滿足pretime===0
,leading===false
,就會將pretime賦值為當前時間now)。 - 當設置了
leading:false
,trailing: false
禁止第一次執行,禁止停止觸發后再執行一次,當前時間pretime的初始值為now(達到禁止第一次執行的效果)同時不會使用定時器的方式。
6. 小結
本篇文章中,實現了節流的四個版本:時間戳實現,定時器實現,組合實現,自定義實現。我們最后實現的版本與underscore中的節流是一樣的,在underscore中還添加了一個取消節流的函數,實現方法也很簡單,有興趣的同學可以去這個鏈接了解一下underscore/throttle.js
今天的文章就到這里啦,我們下次再見~