第 3 章 閉包和高階函數

第一部分 基礎知識javascript

第 3 章 閉包和高階函數html

3.1 閉包java

3.1.1 變量的做用域node

  變量的做用域,就是指變量的有效範圍。編程

//做用域範圍
	var func = function(){
		var a = 1;
		alert ( a ); // 輸出: 1
	};
	func();
	alert ( a ); // 輸出:Uncaught ReferenceError: a is not defined
        //嵌套函數
        var a = 1;
	var func1 = function(){
		var b = 2;
	var func2 = function(){
		var c = 3;
		alert ( b ); // 輸出:2
		alert ( a ); // 輸出:1
	}
	func2();
		alert ( c ); // 輸出:Uncaught ReferenceError: c is not defined
	};
	func1();            

3.1.2 變量的生存週期設計模式

  對於全局變量來講,全局變量的生存週期是永久的;瀏覽器

  而對於在函數內用 var 關鍵字聲明的局部變量來講,當退出函數時,這些局部變量即失去了它們的價值,它們都會隨着函數調用的結束而被銷燬。安全

        //生命週期
	var func = function(){
		var a = 1; // 退出函數後局部變量 a 將被銷燬
		alert ( a );
	};
	func();

  

3.1.3 閉包的更多做用閉包

  1. 封裝變量app

        //1. 封裝變量
	var mult = (function(){
		var cache = {};
		var calculate = function(){
			var a = 1;
			for(var i = 0,l = arguments.length;i<l;i++){
				a = a * arguments[i];
				console.log(i)
			}
			return a;
		}
		return function(){
			var args = Array.prototype.join.call(arguments, ',');
			if(args in cache){
				return cache[args];
			}
			return cache[args] = calculate.apply(null, arguments);
		}
	})()
	console.log(mult( 1,2,3 ))
	console.log(mult( 1,2,3 ))

  2. 延續局部變量的壽命

3.1.4 閉包和麪向對象設計

3.1.5 用閉包實現命令模式

  命令模式的意圖是把請求封裝爲對象,從而分離請求的發起者和請求的接收者(執行者)之間的耦合關係。在命令被執行以前,能夠預先往命令對象中植入命令的接收者。

<!--命令模式-->
<div>
	<button id='open'>打開</button>
	<button id='close'>關閉</button>
</div>

  

//命令模式
	var Tv = {
		open:function(){
			console.log("打開電視機");
		},
		close:function(){
			console.log("關閉電視機");
		}
	}

	var creatCommand = function(receiver){
		var open = function(){
			return receiver.open();
		}
		var close = function(){
			return receiver.close();
		}

		return {
			open:open,
			close:close
		}
	}

	var setCommand = function(command){
		document.getElementById("open").onclick = function(){
			command.open();
		}
		document.getElementById("close").onclick = function(){
			command.close();
		}
	}

	setCommand(creatCommand(Tv))

3.1.6 閉包與內存管理

  若是要解決循環引用帶來的內存泄露問題,咱們只須要把循環引用中的變量設爲 null便可。

3.2 高階函數

  高階函數是指至少知足下列條件之一的函數。

3.2.1 函數能夠做爲參數被傳遞;

    1. 回調函數

    2.  Array.prototype.sort
3.2.2 函數能夠做爲返回值輸出。

    1. 判斷數據的類型 

    //判斷數據的類型
	var isType = function( type ){
		return function( obj ){
			return Object.prototype.toString.call( obj ) === '[object '+ type +']';
		}
	};
	var isString = isType( 'String' );
	var isArray = isType( 'Array' );
	var isNumber = isType( 'Number' );
	console.log( isArray( [ 1, 2, 3 ] ) ); // 輸出:true

  

	//判斷數據的類型
	var Type = {};
	for(var i=0,type;type=['String','Array','Number'][i++];){
		(function(type){
			Type['is'+type] = function(obj){
				return Object.prototype.toString.call(obj) === '[object '+type+']';
			}
		})(type)
	}
	console.log(Type.isArray([ 1, 2, 3 ] ));
	console.log(Type.isString('str'));

    2.  getSingle單例模式

	var getSingle = function(fn){
		var ret;
		return function(){
			return ret || (ret = fn.apply(this, arguments));
		}
	}

	var getScript = getSingle(function(){
		return document.createElement('script');
	})
	var script1 = getScript();
	console.log(script1);

3.2.3 高階函數實現AOP

  AOP(面向切面編程)的主要做用是把一些跟核心業務邏輯模塊無關的功能抽離出來,包括日誌統計、安全控制、異常處理等。好處是保持業務邏輯模塊的純淨和高內聚性。

在JavaScript中實現AOP,都是指把一個函數「動態織入」到另外一個函數之中。

//經過擴展 Function.prototype來實現面向切面
	Function.prototype.before = function(beforefn){
		var _self = this; //保存原函數的引用
		//返回包含原函數和新函數的「代理」函數
		return function(){
			beforefn.apply(this, arguments); //執行新函數,修正this
			return _self.apply(this,arguments); //執行原函數
		}
	}

	Function.prototype.after = function(afterfn){
		var _self = this;
		return function(){
			var ref = _self.apply(this, arguments);
			afterfn.apply(this.arguments);
			return ref;
		}
	}

	var func = function(){
		console.log(2);
	}

	func = func.before(function(){
		console.log(1);
	}).after(function(){
		console.log(3);
	})
	func();

3.2.4 高階函數的其餘應用

  1.  currying

  函數柯里化(function currying):currying又稱部分求值。一個currying的函數首先會接受一些參數,接受了這些參數後,該函數不會當即求值,而是返回另外一個函數,剛纔傳入的參數在函數中造成的閉包中被保存起來。待到函數被真正須要求值的時候,以前傳入的全部參數都會被一次性求值。

	var currying = function(fn){
		var args = [];
		return function(){
			if(arguments.length === 0){
				return fn.apply(this, args);
			}else{
				[].push.apply(args,arguments);
				return arguments.callee;
			}
		}
	}
	var cost = (function(){
		var money=0;
		return function(){
			for(var i = 0,l=arguments.length;i<l;i++){
				money += arguments[i];
			}
			return money;
		}
	})()

	var cost = currying(cost);//轉化成currying函數
	cost(600);//未真正求值
	cost(900);//未真正求值
	console.log(cost())//求值

  2.  uncurrying

  在javaScript中,當咱們調用對象的某個方法時,其實不用去關心該對象本來是否被設計爲擁有該方法,這是動態類型語方的特色,也就是鴨子類型思想。一個對象未必只能使用它自身的方法,可讓對象去借用一個本來不屬於它的方法。call和apply均可以完成這個需求:

	//借用call、apply
	var obj1 = {
		name:'name1'
	}
	var obj2 = {
		getName:function(){
			return this.name;
		}
	}
	console.log(obj2.getName.apply(obj1));//obj1借用obj2的getName方法

	// Array.prototype
	(function(){
		Array.prototype.push.call(arguments,4,5);//arguments借用Array.prototype.push方法
		console.log(arguments)
	})(1,2,3)

  

	//把泛化this的過程提取出來
	Function.prototype.currying = function(){
		var _self = this;// self 此時是 Array.prototype.push
		return function(){
			var obj = Array.prototype.shift.call(arguments);//arguments 對象的第一個元素被截去
			return _self.apply(obj,arguments);// 至關於 Array.prototype.push.apply( obj, 2 )
		}
	}
	for(var i=0,fn, ary = ['push','shift','forEach'];fn=ary[i++];){
		Array[fn] = Array.prototype[fn].currying();
	}

	var obj = {
		'length':'3',
		'0':1,
		'1':2,
		'2':3
	}

	Array.push(obj,4);//向對象中添加一個元素
	console.log(obj);

	var first = Array.shift(obj);//截取第一個元素
	console.log(first);//輸出:1

	Array.forEach(obj,function(item,i){
		console.log(item);//輸出2,3,4
	})

  

	//uncurrying另外一種實現方式
	Function.prototype.currying = function(){
		var _self = this;
		return function(){
			return Function.prototype.call.apply(_self,arguments);
		}
	}

  3. 函數節流

  函數的觸發不是由用戶直接控制的時候,函數有可能會頻繁的被調用,形成大的性能問題。

  (1)函數被頻繁調用的場景:

   window.onresize 事件。當瀏覽器窗口大小被拖動而改變的時候。

   mousemove 事件。拖 拽事件。

    上傳進度。

  (2) 函數節流的原理:

  上述三個場景面臨的共同問題是函數被觸發頻率過高。能夠藉助setTimeout來完成這件事情。

  (3) 函數節流的代碼實現

	//函數節流
	var throttle = function(fn,interval){
		var _self=fn,//保存須要被延遲執行的函數
			timer,//定時器
			firstTime = true;//是否第一次執行函數

		return function(){
			var args = arguments,
				_me = this;
			if(firstTime){//若是是第一次執行不須要延遲執行
				_self.apply(_me, args);
				return firstTime=false;
			}
			if(timer){//若是定時器還在,說明前一次延遲執行尚未完成
				return false;
			}

			timer = setTimeout(function(){//延遲時間段執行
				clearTimeout(timer);
				timer = null;
				_self.apply(_me, args);

			}, interval || 500)
		}
	}

  4. 分時函數

  因爲用戶調用的,致使一些函數嚴重影響頁面性能。

	/*
	* 分時函數
	* ary:建立節點時用到的數據
	* fn:封裝建立節點輯的函數
	* count每一批建立節點的數量
	*/
	var timeChunk = function(ary, fn, count){
		var timer;
		var start = function(){
			for(var i = 0;i<Math.min(count || 1,ary.length);i++){
				var obj = ary.shift();
				fn(obj);
			}
		};
		return function(){
			timer = setInterval(function(){
				if(ary.length === 0){//若是所有節點建立完畢
					return clearInterval(timer);
				}
				start();
			},200)
		}
	

	var ary = [];

	for(var i = 0;i<1000;i++){
		ary.push(i);
	}

	var renderFriendList = timeChunk(ary,function(node){
		var div = document.createElement('div');
		div.innerHTML = node;
		document.body.appendChild(div);
	},8);

	renderFriendList();

  5. 惰性加載函數

  避免每次執行都會進行判斷,讓程序避免這些重複的執行過程。

	//惰性加載函數
	var addEvent = function(elem, type, handler){
		if(window.addEventListener){
              //對addEvent函數進行重寫,當下次再進入addEvent的時候再也不重複進行判斷 addEvent = function(elem, type, handler){ elem.addEventListener(type, handler, false); } }else if(window.attachEvent){ addEvent = function(elem, type, handler){ elem.attachEvent('on'+type,handler,false); } } addEvent(elem,type,handler); }; var div = document.getElementById('div1'); addEvent(div,'click',function(){ console.log(1); }); addEvent(div,'click',function(){ console.log(2); });

 3.3 小結

  許多設計模式在 JavaScript 之中的實現跟在一些傳統面嚮對象語言中的實現相差很大。在JavaScript 中,不少設計模式都是經過閉包和高階函數實現的。

相關文章
相關標籤/搜索