淺談javascript函數節流
什么是函數節流?
函數節流簡單的來說就是不想讓該函數在很短的時間內連續被調用,比如我們最常見的是窗口縮放的時候,經常會執行一些其他的操作函數,比如發一個ajax請求等等事情,那么這時候窗口縮放的時候,有可能連續發多個請求,這並不是我們想要的,或者是說我們常見的鼠標移入移出tab切換效果,有時候連續且移動的很快的時候,會有閃爍的效果,這時候我們就可以使用函數節流來操作。大家都知道,DOM的操作會很消耗或影響性能的,如果是說在窗口縮放的時候,為元素綁定大量的dom操作的話,會引發大量的連續計算,比如在IE下,過多的DOM操作會影響瀏覽器性能,甚至嚴重的情況下,會引起瀏覽器崩潰的發生。這個時候我們就可以使用函數節流來優化代碼了~
函數節流的基本原理:
使用一個定時器,先延時該函數的執行,比如使用setTomeout()這個函數延遲一段時間后執行函數,如果在該時間段內還觸發了其他事件,我們可以使用清除方法 clearTimeout()來清除該定時器,再setTimeout()一個新的定時器延遲一會兒執行。
我們先來看一個簡單的window.resize的demo例子,比如我先定義一個全局變量count=0;當我觸發一次window.resize的時候,該全局變量count++; 我們來看看在控制台中打印出count的效果;JS代碼如下:
var count = 0; window.onresize = function(){ count++; console.log(count); }
執行截圖效果如下:
如上resize的代碼,簡單的縮放一次就打印出多次,這並不是我們想要的效果,這是簡單的測試,那如果我們換成ajax請求的話,那么就會縮放一次窗口會連續觸發多次ajax請求,下面我們試着使用函數節流的操作試試一下;
函數節流的第一種方案封裝如下:
function throttleFunc(method,context){ clearTimeout(method.tId); method.tId = setTimeout(function(){ method.call(context); },100); }
我們再來封裝一下窗口縮放的demo
var count = 0; function myFunc() { count++; console.log(count); } window.onresize = function(){ throttleFunc(myFunc); } function throttleFunc(method,context){ clearTimeout(method.tId); method.tId = setTimeout(function(){ method.call(context); },100); }
如上代碼,我們再來看看效果,窗口縮放和放大效果會看到,只執行了一次;打印了一次。
上面的代碼使用一個定時器每隔100毫秒執行一次;
我們也可以使用閉包的方法對上面的函數進行再封裝一下;
函數節流的第二種封裝方法如下:
function throttle(fn, delay){ var timer = null; return function(){ var context = this, args = arguments; clearTimeout(timer); timer = setTimeout(function(){ fn.apply(context, args); }, delay); }; };
上面第二種方案是使用閉包的方式形成一個私有的作用域來存放定時器timer,第二種方案的timer是通過傳參數的形式引入的。
調用demo代碼如下:
var count = 0; function myFunc() { count++; console.log(count); } var func = throttle(myFunc,100); window.onresize = function(){ func(); } function throttle(fn, delay){ var timer = null; return function(){ var context = this, args = arguments; clearTimeout(timer); timer = setTimeout(function(){ fn.apply(context, args); }, delay); }; };
函數節流的基本思想是:就是想讓一個函數不要執行的太頻繁,減少一些過快的來節流函數,比如當我們改變窗口縮放的時候,瀏覽器的間隔有可能是16ms,這是瀏覽器自帶的時間間隔,我們無法改變,而我們通過節流的方式可以試着改變一下這個間隔,盡量稍微延長下這個調用時間,因此我們可以封裝如下函數:
函數節流的第三種封裝方法
function throttle3(fn,delay,runDelay){ var timer = null; var t_start; return function(){ var context = this, args = arguments, t_cur = new Date(); timer && clearTimeout(timer); if(!t_start) { t_start = t_cur; } if(t_cur - t_start >= runDelay) { fn.apply(context,args); t_start = t_cur; }else { timer = setTimeout(function(){ fn.apply(context,args); },delay); } } }
調用demo如下:
var count = 0; function myFunc() { count++; console.log(count); } var func = throttle3(myFunc,50,100); window.onresize = function(){ func();
} function throttle3(fn,delay,runDelay){ var timer = null; var t_start; return function(){ var context = this, args = arguments, t_cur = new Date(); timer && clearTimeout(timer); if(!t_start) { t_start = t_cur; } if(t_cur - t_start >= runDelay) { fn.apply(context,args); t_start = t_cur; }else { timer = setTimeout(function(){ fn.apply(context,args); },delay); } } }
上面的第三個函數是封裝后的函數,有三個參數,我們可以自己設置觸發事件的時間間隔,則意味着,如上代碼50ms連續調用函數,后一個調用會把前一個調用的等待處理掉,但每隔100ms會至少執行一次,具體使用哪一種方式只要看自己的權衡,但是我個人覺得第二種封裝函數的方式夠我們使用的,當然據說第三種方式性能更好~