js中的閉包

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>閉包</title>
</head>
<body>
	
</body>
</html>
<script type="text/javascript">
	//要理解閉包,首先必須理解Javascript特殊的變量作用域。
	//變量的作用域無非就是兩種:全局變量和局部變量。
	//Javascript語言的特殊之處,就在於函數內部可以直接讀取全局變量。

	//Js代碼
  //var a=666;
  //function fn(){
	//	console.info(a);
  //}
  //fn(); // 666



	//另一種情況,在函數外部自然無法讀取函數內的局部變量。
	//Js代碼
  //function fn(){
   // var b=666;
  //}
  //console.info(b); // b is not defined
	

	//這裏有一個地方需要注意,函數內部聲明變量的時候,一定要使用var命令。如果不用的話,
	//你實際上聲明瞭一個全局變量!全局變量是可以全局訪問的。
	//Js代碼
  //function fn(){
  //  c=6;
  //}
  //fn();
  //console.info(c); // 6

	// 閉包的結構:
	// 出於種種原因,我們有時候需要得到函數內的局部變量。
	// 但是,前面已經說過了,正常情況下,這是辦不到的,只有通過變通方法才能實現。
	// 那就是在函數的內部,再定義一個函數。

	// 在下面的代碼中,函數f2就被包括在函數f1內部,這時f1內部的所有局部變量,對f2都是可見的。
	// 但是反過來就不行,f2內部的局部變量,對f1 就是不可見的。這就是	Javascript語言特有的“鏈式作用域”結構(chain scope),
	// 子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。
	// 既然f2可以讀取f1中的局部變量,那麼只要把f2作爲返回值,我們不就可以在f1外部讀取它的內部變量了嗎!
	//Js代碼
  //function f1(){
  //  n=666;
  //  function f2(){
  //    alert(n); // 666
  //  }
  //}
	
	//真正的閉包代碼結構
	// Js代碼
  //function f1(){
  //  n=888;
  //  function f2(){
	//		console.info(n);
  //  }
  //  return f2();
  //}
  //f1();// 888

	//閉包的概念
	//上一節代碼中的f2函數,就是閉包。
	//各種專業文獻上的“閉包”(closure)定義非常抽象,很難看懂。我的理解是,閉包就是能夠讀取其他函數內部變量的函數。
	//由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成“定義在一個函數內部的函數”。
	//所以,在本質上,閉包就是將函數內部和函數外部連接起來的一座橋樑。


	// 閉包的用途
	// 閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。	
	//Js代碼
  //function f1(){
  //  var n=999;
  //  nAdd=function(){n+=1}
  //  function f2(){
  //    alert(n);
  //  }
  //  return f2;
  //}
  //var result=f1();
  //result(); // 999
  //nAdd();
  //result(); // 1000
	//在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次的值是999,第二次的值是1000。
	//這證明了,函數f1中的局部變量n一直保存在內存中,並沒有在f1調用後被自動清除。
	//爲什麼會這樣呢?原因就在於f1是f2的父函數,而f2被賦給了一個全局變量,這導致f2始終在內存中,而f2的存在依賴於f1,
	//因此f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。
	//這段代碼中另一個值得注意的地方,就是“nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關鍵字,
	//因此 nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個
	//匿名函數本身也是一個閉包,所以nAdd相當於是一個setter,可以在函數外部對函數內部的局部變量進行操作。


	//使用閉包的注意點
	//1)由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露//。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。
	//2)閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method//),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便
	//改變父函數內部變量的值。



	//如果你能理解下面代碼的運行結果,應該就算理解閉包的運行機制了。
	//Js代碼 
  //var name = "The Window";   
  //var object = {   
  //   name : "My Object",   
  //   getNameFunc : function(){   
  //     return function(){   
  //       return this.name;   
  //    };   
  //   }   
 	//};   
 	//alert(object.getNameFunc()()); 




	//Js代碼
	//function outerFun(){
	//	var a =0;
	//	alert(a);  
	//}
	//var a=4;
	//outerFun();
	//alert(a);// 0 4




	//function outerFun(){
	//	a =0;
	//	alert(a);  
	//}
	//var a=4;
	//outerFun();
	//alert(a);//0 0 
	//作用域鏈是描述一種路徑的術語,沿着該路徑可以確定變量的值 .
	//當執行a=0時,因爲沒有使用var關鍵字,因此賦值操作會沿着作用域鏈到var a=4;  並改變其值.
	

	//function a() { 
	//	var i = 0; 
	//	function b() { alert(++i); } 
	//	return b;
	//}
	//var c = a();
	//c();//1   
	//函數b嵌套在函數a內部;函數a返回函數b。
	//這樣在執行完var c=a()後,變量c實際上是指向了函數b,再執行c()後就會彈出一個窗口顯示i的值(第一次爲1)。這段代碼其實就創建了一個閉包,爲什麼?
	//因爲函數a外的變量c引用了函數a內的函數b,就是說:
  //當函數a的內部函數b被函數a外的一個變量引用的時候,就創建了一個閉包。
  //讓我們說的更透徹一些。所謂“閉包”,就是在構造函數體內定義另外的函數作爲目標對象的方法函數,而這個對象的方法函數反過來引用外層函數體中的臨時變量。
	//這使得只要目標 對象在生存期內始終能保持其方法,就能間接保持原構造函數體當時用到的臨時變量值。儘管最開始的構造函數調用已經結束,
	//臨時變量的名稱也都消失了,但在目 標對象的方法內卻始終能引用到該變量的值,
	//而且該值只能通這種方法來訪問。即使再次調用相同的構造函數,但只會生成新對象和方法,新的臨時變量只是對應新 的值,和上次那次調用的是各自獨立的。
	

	//閉包有什麼作用?:
  //簡而言之,閉包的作用就是在a執行完並返回後,閉包使得Javascript的垃圾回收機制GC不會收回a所佔用的資源,
	//因爲a的內部函數b的執行需要依賴a中的變量。這是對閉包作用的非常直白的描述,不專業也不嚴謹,但大概意思就是這樣,理解閉包需要循序漸進的過程。
	//在上面的例子中,由於閉包的存在使得函數a返回後,a中的i始終存在,這樣每次執行c(),i都是自加1後alert出i的值。
  //那麼我們來想象另一種情況,如果a返回的不是函數b,情況就完全不同了。因爲a執行完後,b沒有被返回給a的外界,只是被a所引用,而此時a也只會被b引 用,
	//因此函數a和b互相引用但又不被外界打擾(被外界引用),函數a和b就會被GC回收。(關於Javascript的垃圾回收機制將在後面詳細介紹)


	//閉包內的微觀世界:
  //如果要更加深入的瞭解閉包以及函數a和嵌套函數b的關係,我們需要引入另外幾個概念:函數的執行環境(excution context)、
	//活動對象(call object)、作用域(scope)、作用域鏈(scope chain)。以函數a從定義到執行的過程爲例闡述這幾個概念。
	//當定義函數a的時候,js解析器會將函數a的作用域鏈(scope chain)設置爲定義a時a所在的“環境”,如果a是一個全局函數,則scope chain中只有window對象。
	//當執行函數a的時候,a會進入相應的執行環境(excution context)。
	//在創建執行環境的過程中,首先會爲a添加一個scope屬性,即a的作用域,其值就爲第1步中的scope chain。即a.scope=a的作用域鏈。
	//然後執行環境會創建一個活動對象(call object)。活動對象也是一個擁有屬性的對象,但它不具有原型而且不能通過JavaScript代碼直接訪問。
	//創建完活動對象後,把活動對象添加到a的作用域鏈的最頂端。此時a的作用域鏈包含了兩個對象:a的活動對象和window對象。
	//下一步是在活動對象上添加一個arguments屬性,它保存着調用函數a時所傳遞的參數。
	//最後把所有函數a的形參和內部的函數b的引用也添加到a的活動對象上。在這一步中,完成了函數b的的定義,因此如同第3步,
	//函數b的作用域鏈被設置爲b所被定義的環境,即a的作用域。
	//到此,整個函數a從定義到執行的步驟就完成了。此時a返回函數b的引用給c,又函數b的作用域鏈包含了對函數a的活動對象的引用,
	//也就是說b可以訪問到a中定義的所有變量和函數。函數b被c引用,函數b又依賴函數a,因此函數a在返回後不會被GC回收。


	//Javascript的垃圾回收機制:
	//在Javascript中,如果一個對象不再被引用,那麼這個對象就會被GC回收。如果兩個對象互相引用,
	//而不再被第3者所引用,那麼這兩個互相引用的對象也會被回收。因爲函數a被b引用,b又被a外的c引用,這就是爲什麼函數a執行後不會被回收的原因。

</script>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章