1. 程式人生 > >js節流和防抖別看這一篇

js節流和防抖別看這一篇

  前言:我們在做頁面事件繫結的時候,經常要進行節流處理,比如滑鼠非同步點選,去執行一個非同步請求時,需要讓它在上一次沒執行時不能再點選,又或者繫結滾動事件,這種持續觸發進行dom判斷的時候,就要按一定頻率的執行。

一、 偽理論:

     概念: 節流和防抖我認為都可以稱之為節流。區別:防抖就是不想持續操作的節流,節流就是想固定頻率執行的。
 節流:函式節流是指一定時間內js方法只跑一次。前一次沒結束,後一次不會跑。比如滾動條這種持續按頻率觸發。
    防抖:函式防抖是指一定時間內js方法只跑一次。前一次沒結束,後一次觸發會按後一次的新間隔時間計算。比如按鈕持續點選的限速。

二、原始的節流操作:

<!-- 一、最原始進行節流操作的函式 -->
<script type="text/javascript">
	window.onload = function(){
		
		/**基本防抖案例:先清除,後setTimeout進行執行**/
		document.querySelector("#send").addEventListener("click",function(e){
			clearTimeout(window.mytime_01);
			window.mytime_01 = setTimeout(function(){ 
				console.log("發射...");
			}, 500);
		});
		
		/**基本節流案例:setTimeout執行完時,恢復標誌位,下一次才能執行*/
		var mytime_02 = true;
		window.onscroll = function(e) {
			if(!mytime_02){return;} //首次進入能執行
			mytime_02 = false;
			setTimeout(function(){ 
				console.log("滾..."); 
				mytime_02 = true; //上次執行成功,下一次才可執行。
			}, 200);
		}
		
		/**測試:二、underscore.js進行節流操作的函式**/
		document.querySelector("#send2").addEventListener("click", _.debounce(function() {
			console.log("點選2");
		},1000));
		
	};
		
</script>
<body style="height:800px;">
	<div style="width:200px;height:50px;background:#333" id="send">點我</div>
	<div style="width:200px;height:50px;background:#ccc" id="send2">點我2</div>
</body>

 從上面我們可以進行簡單的限流,但是存在一個問題,我們這個程式碼和事件函式嚴重耦合在一次,每個事件有節流需要,就需要在方法內固定的新增那幾句程式碼,我可以慚愧的告訴你,我做的一個專案,裡面節流基本全是這樣手工一個一個加上去的。

  那麼市面上有沒有比較成熟的庫中有這二個方法呢? underscore.js 就有現成的這二個方法。上面的測試二,就是呼叫此js的節流。

  三、underscore.js 的節流方法的原始碼閱讀。

<!-- 二、underscore.js進行節流操作的函式 -->
<script type="text/javascript">

	  var _ = {};
	
	 _.now = function (){return new Date().getTime()};
	
	  /**防抖: func 執行的方法,wait 等待時間 ,immediate true表示立即執行(此處邏輯程式碼已經刪除)**/
	  _.debounce = function(func, wait, immediate){
	    var timeout, args, context, timestamp, result; //這些變數和方法引數是所有執行事件的函式共享!!!
	    var later = function(){
	    	//下面三行程式碼是核心。
		    var last = _.now() - timestamp; //timestamp當事件觸發就會被改變,last預期就不等於wait.
		    if (last < wait && last >= 0) { //timestamp被改變了,照成last不夠wait,就繼續setTimeout(...,最後次事件剩餘的wait)
		        timeout = setTimeout(later, wait - last); //wait-last到期後 --> 等價於 timestamp + wait 的時間。 
		    }else{
		        timeout = null;
		        result = func.apply(context, args);
		        if (!timeout) context = args = null;
		    }
	    };
	    return function(){ /**觸發事件返回閉包函式,可以操作func,wait,以及設定的幾個全域性變數和later方法**/
	      context = this; //事件呼叫的話,this就是dom物件
	      args = arguments; //事件呼叫的話,就是事件的e引數
	      timestamp = _.now(); //最後次事件觸發的時間戳。
	      if (!timeout) //只有timeout沒有初始化的時候,才會設定setTimeout。
	         timeout = setTimeout(later, wait); //啟動延遲執行,later內部會遞迴呼叫。
	      return result;
	    };
	  };
	  	
	/**節流: func 執行的方法,wait 等待時間  **/
   _.throttle = function(func, wait, options){
	    var context, args, result;
	    var timeout = null;
	    var previous = 0;
	    if (!options)
	        options = {};
	    var later = function() {
	      previous = options.leading === false ? 0 : _.now();
	      timeout = null;
	      result = func.apply(context, args);
	      if (!timeout) context = args = null;
	    };
	    return function() {
	      var now = _.now();
	      if (!previous && options.leading === false) previous = now;
	      var remaining = wait - (now - previous);
	      context = this;
	      args = arguments;
	      if (remaining <= 0 || remaining > 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, remaining);
	      }
	      return result;
	    };
  };
	
</script>

underscore實現防抖核心思路:是利用閉包函式返回作為執行函式,共享timeout引數進行標記判斷,並且利用觸發事件會共同操作時間戳timestamp 來重新用setTimeout方式遞迴調整新的啟動時間。