防抖函數和節流函數本質是不同的。防抖函數是將屢次執行變爲最後一次執行或第一次執行,節流函數是將屢次執行變成每隔一段時間執行。javascript
防抖函數和節流函數本質是不同的。防抖函數是將屢次執行變爲最後一次執行或第一次執行,節流函數是將屢次執行變成每隔一段時間執行。html
好比說,噹噹咱們作圖片懶加載(lazyload)時,須要經過滾動位置,實時顯示圖片時,若是使用防抖函數,懶加載(lazyload)函數將會不斷被延時, 當咱們作圖片懶加載(lazyload)時,須要經過滾動位置,實時顯示圖片時,若是使用防抖函數,懶加載(lazyload)函數將會不斷被延時, 只有停下來的時候纔會被執行,對於這種須要週期性觸發事件的狀況,防抖函數就顯得不是很友好了,此時就應該使用節流函數來實現了。前端
<div id="container"></div>
複製代碼
div{
height: 200px;
line-height: 200px;
text-align: center; color: #fff;
background-color: #444;
font-size: 25px;
border-radius: 3px;
}
複製代碼
let count = 1;
let container = document.getElementsByTagName('div')[0];
function updateCount() {
container.innerHTML = count ++ ;
}
container.addEventListener('mousemove',updateCount);
複製代碼
咱們來看一下效果:java
咱們能夠看到,鼠標從左側滑到右側,咱們綁定的事件執行了119次git
如今咱們來實現一個節流函數,使得鼠標移動過程當中每間隔一段時間事件觸發一次。github
首先咱們想到使用時間戳計時的方式,每次事件執行時獲取當前時間並進行比較判斷是否執行事件。app
/** * 節流函數 * @param func 用戶傳入的節流函數 * @param wait 間隔的時間 */
const throttle = function (func,wait = 50) {
let preTime = 0;
return function (...args) {
let now = Date.now();
if(now - preTime >= wait){
func.apply(this,args);
preTime = now;
}
}
};
複製代碼
let count = 1;
let container = document.getElementsByTagName('div')[0];
function updateCount() {
container.innerHTML = count ++ ;
}
let action = throttle(updateCount,1000);
container.addEventListener('mousemove',action);
複製代碼
此時當鼠標移入的時候,事件當即執行,在鼠標移動的過程當中,每隔1000ms事件執行一次,旦在最後鼠標中止移動後,事件不會被執行
此時會有這樣的兩個問題:函數
爲知足上面的需求,咱們考慮使用定時器來實現節流函數
當事件觸發的時候,咱們設置一個定時器,再觸發的時候,定時器存在就不執行,等到定時器執行並執行函數,清空定時器,而後接着設置定時器優化
/**
* 節流函數
* @param func 用戶傳入的節流函數
* @param wait 間隔的時間
*/
const throttle = function (func,wait = 50) {
let timer = null;
return function (...args) {
if(!timer){
timer = setTimeout(()=>{
func.apply(this,args);
timer = null;
},wait);
}
}
};
複製代碼
使用這個定時器節流函數應用在最開始的例子上:ui
let action = throttle(updateCount,2000);
container.addEventListener('mousemove',action);
複製代碼
咱們能夠看到,當鼠標移入的時候,時間不會當即執行,等待2000ms後執行了一次,此後2000ms執行一次,當鼠標移除後,前一次觸發事件的時間2000ms後還會觸發一次事件。
對於咱們平常的工做需求來講,可能出現的需求是,既須要開始時當即執行,也須要結束時還能再執行一次的節流函數。
/** * 節流函數 * @param func 用戶傳入的節流函數 * @param wait 間隔的時間 */
const throttle = function (func,wait = 50) {
let preTime = 0,
timer = null;
return function (...args) {
let now = Date.now();
// 沒有剩餘時間 || 修改了系統時間
if(now - preTime >= wait || preTime > now){
if(timer){
clearTimeout(timer);
timer = null;
}
preTime = now;
func.apply(this,args);
}else if(!timer){
timer = setTimeout(()=>{
preTime = Date.now();
timer = null;
func.apply(this,args)
},wait - now + preTime);
}
}
};
複製代碼
使用這個定時器節流函數應用在最開始的例子上:
let action = throttle(updateCount,2000);
container.addEventListener('mousemove',action);
複製代碼
咱們能夠看到,當鼠標移入時,事件當即執行,以後每間隔2000ms後,事件均會執行,當鼠標離開時,前一次事件觸發2000ms後,事件最後會再一次執行
咱們繼續考慮下面的場景
咱們設置 opts 做爲 throttle 函數的第三個參數,而後根據 opts 所攜帶的值來判斷實現那種效果,約定以下:
修改代碼以下:
/** * 節流函數 * @param func 用戶傳入的節流函數 * @param wait 間隔的時間 * @param opts leading 是否第一次執行 trailing 是否中止觸發後執行 */
const throttle = function (func,wait = 50,opts = {}) {
let preTime = 0,
timer = null,
{ leading = true, trailing = true } = opts;
return function (...args) {
let now = Date.now();
if(!leading && !preTime){
preTime = now;
}
// 沒有剩餘時間 || 修改了系統時間
if(now - preTime >= wait || preTime > now){
if(timer){
clearTimeout(timer);
timer = null;
}
preTime = now;
func.apply(this,args);
}else if(!timer && trailing){
timer = setTimeout(()=>{
preTime = Date.now();
timer = null;
func.apply(this,args)
},wait - now + preTime);
}
}
};
複製代碼
這裏須要注意的是,leading:false 和 trailing: false 不能同時設置。 由於若是同時設置的時候,當鼠標移除的時候,中止觸發的時候不會設置定時器,也就是說,等到過了設置的時間,preTime不會被更新,此後再次移入的話就會當即執行,就違反了 leading: false
在 debounce 的實現中,咱們加了一個 cancel 方法,throttle 咱們也加個 cancel 方法:
/**
* 節流函數
* @param func 用戶傳入的節流函數
* @param wait 間隔的時間
* @param opts leading 是否第一次執行 trailing 是否中止觸發後執行
*/
const throttle = function (func,wait = 50,opts = {}) {
let preTime = 0,
timer = null,
{ leading = false, trailing = true } = opts,
throttled = function (...args) {
let now = Date.now();
if(!leading && !preTime){
preTime = now;
}
// 沒有剩餘時間 || 修改了系統時間
if(now - preTime >= wait || preTime > now){
if(timer){
clearTimeout(timer);
timer = null;
}
preTime = now;
func.apply(this,args);
}else if(!timer && trailing){
timer = setTimeout(()=>{
preTime = Date.now();
timer = null;
func.apply(this,args)
},wait - now + preTime);
}
};
throttled.cancel = function () {
clearTimeout(timer);
timer = null;
preTime = 0;
};
return throttled;
};
複製代碼
至此咱們完成了一個節流函數。