原文地址: https://www.jeremyjone.com/705/,轉載請註明。
什麼是節流
在函數調用過程中,避免過於頻繁的調用,而是間隔某一時間後調用一次的方法,叫做節流。
節流做什麼
節流可以有效避免短時間內大量調用某一方法或數據,保證頁面的穩定性和數據的準確性。
一個小的例子
使用 underscore 的節流功能來測試一下效果。
在頁面中直接導入 cdn 即可。
https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js
未節流時的樣子
將下面內容粘貼到一個 HTML 的 body 標籤中。
<div
id="container"
style="width:100%;height:200px;line-height:200px;text-align:center;color:#fff;background-color:#444;font-size:30px;"
></div>
<script>
let count = 0;
let container = document.querySelector("#container");
// 此處爲高頻調用函數
function doSomething() {
container.innerHTML = count++;
}
container.onmousemove = doSomething;
</script>
這段代碼會生成一個灰色框,只要鼠標在其內部移動,就會調用 doSomething
函數,導致數字不斷增長。
使用節流
同樣地,將下面內容粘貼到一個新的 HTML 的 body 標籤中。
<div
id="container"
style="width:100%;height:200px;line-height:200px;text-align:center;color:#fff;background-color:#444;font-size:30px;"
></div>
<script src="https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js"></script>
<script>
let count = 0;
let container = document.querySelector("#container");
// 此處爲高頻調用函數
function doSomething() {
container.innerHTML = count++;
}
container.onmousemove = _.throttle(doSomething, 300);
</script>
這裏導入了
underscore
庫,這個庫會導出一個_
的對象,它包含一個throttle
方法,該方法的作用就是節流。第一個參數是函數原型,第二個參數是響應時間,這裏我們設置 300ms 後響應。另外還可以設置是否第一次首先執行和最後一次是否執行,傳入:
{leading: false}
或{trailing: false}
。當然,你也可以同時傳入,但是需要注意,如果同時傳入false
的話,會出現 bug,該 bug 會導致下次啓動時不響應{leading: false}
,而是會立即執行第一次,這一點需要注意。
這次運行後,可以發現當鼠標移動時,不再一味地增加,而是每隔一段時間後(300ms)纔會響應一次。
手動實現節流函數
利用時間戳方式
這樣的方式會觸發第一次,而不會觸發最後一次。
function throttle(func, wait) {
let context, args;
let old = 0;
return function () {
context = this;
args = arguments;
let now = Date.now();
if (now - old > wait) {
// 立即執行
func.apply(context, args);
old = now;
}
};
}
利用定時器方式
這樣的方式會觸發最後一次,而不會觸發第一次。
function throttle(func, wait) {
let context, args, timeout;
return function () {
context = this;
args = arguments;
// 設置一個定時器,只有爲空時纔會觸發,每次執行後都會重新設定一個定時器。
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args);
}, wait);
}
};
}
合併爲最終的完整本版
利用上面的兩種實現方式,可以得到一個比較完整的版本。
function throttle(func, wait, options) {
let context, args, timeout, result;
let previous = 0;
if (!options) options = {};
let later = function () {
// 這裏控制再次出發時,第一次是否執行,當爲雙false時,也是這裏會出現的問題。
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function () {
context = this;
args = arguments;
let now = Date.now();
// 第一次不執行,調整previous的值即可。
if (!previous && options.leading === false) previous = now;
if (now - previous > wait) {
if (timeout) {
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, wait);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
timeout = null;
timeout = context = args = null;
}
return throttled;
}