一步一步的理解閉包

     一步步的理解閉包:javascript

  1. javascript是函數做用域,按常理來講,一個函數就不能訪問另外一個函數中的變量。
  2. 而咱們實際操做有時候須要用一個函數去操做另外一個函數中的變量。
  3. 爲了可以訪問另外一個函數做用域中的變量,javascript提供一種內部機制,給嵌套在其它函數中的函數提供了一種父做用域鏈保存機制。
  4. 當內部函數建立時,靜態的保存了父做用域鏈,即便父函數執行完畢,其變量對象(這裏面保存着咱們想要的父變量)一直保存在內部函數的做用域鏈中。內部函數再也不被調用以前,這個活動對象一直在內存中(通俗講就是這時候父函數的變量對象和內部函數綁定在一塊兒了,同生共死)。這種機制取名爲閉包。
  5. 簡潔地說:閉包就是有權訪問其餘函數做用域變量的函數。

Figure 6. An execution context structure.

下文的敘述以此圖爲標準,如有疑問請參照java

     下面咱們來一些經典實例來進一步幫你解除閉包的困擾(js中最難的一個問題之一),嵌套函數因爲其自然的嵌套形式是閉包最多見的一種表現方式。由嵌套函數開始吧 緩存

1  function sayHello2(name){
2      var text='hello'+name;
3      var sayAlert=function(){alert(text);}
4      return sayAlert;
5  }
6  var say2=sayHello2('jack');
7  say2();//hello jack

     上面的代碼就是一個嵌套函數,也是咱們見過的最多見閉包表現形式,由於匿名函數function(){alert(text);}能夠訪問到外部函數sayHello2中的text變量,這就造成了閉包。閉包雖然存在了,但它也不能徒有其表,還的作點事情。代碼第6行的賦值表達式後,外部函數sayHello2就被銷燬了,按常理其內部變量text也隨之銷燬。可第7行成功的調用說明,外部函數的變量對象始終保留在內存中,直到內部函數銷燬,再也不被調用爲止(總的有個頭,否則內存扛不住)。閉包

再看下面的例子函數

1 function say667(){
3     var num=666;
4     var sayAlert=function(){alert(num);}
5     num++;
6     return sayAlert;
7 }
8 var sayNum=say667();
9 sayNum();//667

     爲何最後是667,不是你指望的666?(有的地方是這樣解釋的,說是對父變量的引用,而不是複製。筆者認爲基本類型不存在引用(地址)這一說吧)。保存着外部函數的活動對象,活動對象就是一個盒子。裏面的變量,父函數能夠隨意改變。性能

     這個變量對象原本就是父函數的一部分,因此父函數能操做它是理所固然。而這個父變量對象也靜態的保存在內部函數的做用域鏈中,那內部函數應該也有權利去操做它。spa

 1 function baz(){
 2     var x=1;
 3     return {
 4         foo:function foo(){return ++x;},
 5         bar:function bar(){return --x;}
 6             };
 7 }
 8 var closures=baz();
 9 alert(closures.foo(),//2
10     closures.bar()//1)

     你會發現內部的foo函數改變了父變量,與此同時也影響到了bar函數的結果。這是爲何?這是由於內部的兩個函數在建立的時候都保存的是同一個父做用域—baz的做用域。這樣一來父變量對象就是共享的了,因此裏面的變量相互影響。(做用域包含變量對象,變量對象包含變量)code

     再來看咱們遇到過的一個循環問題了,一樣是父函數一某種方式改變了這個變量對象中的變量值。使得內部函數最後訪問的值變成了循環最終值。對象

 1 function list(){
 2     var result=[];
 3     for(var i=0;i<3;i++){
 4         result[i]=function(){
 5             alert(i);
 6         }
 7     }
 8 }
 9 result[0];//3
10 result[1];//3
11 result[2];//3
閉包的用途

下面看看閉包的用途吧,閉包能夠用在許多地方。blog

  1. 基本功能就是前面提到的能夠讀取函數外部的變量,就不在贅述了。
  2. 另外一個就是讓外部函數變量的值始終保持在內存中(直到不存在這些變量的引用)。

     把你指望的值保存在一個地方,這與緩存的思想不謀而合。例如咱們須要處理一個過程很耗時的函數對象,每次調用都會花費很長時間,那麼咱們須要把計算的值保存起來。調用的時候首先在緩存中查找,若是找不到則進行計算,而後更新緩存的值。若是找到了,直接返回找到的值便可。閉包正是能夠作到這一點.

 1 var cacheSearchBox=(function(){
 2     var cache={}; 
4
return { 5 attachSearchBox:function(boxid){ 6 if(boxid in cache){ 7 return cache[boxid]; 8 } 9 var fsb=new searchBox(boxid); 10 cache[boxid]=fsb; 11 return fsb 12 } 13 } 14 })(); 15 cacheSearchBox.attachSearchBox("input1");

     這樣,當咱們第二次調用cacheSearchBox.attachSearchBox("input1")時,咱們能夠從緩存中獲取該對象,而不用再去建立一個新的searchbox對象。

     3 面向對象中實現特權方法

     在下面的例子中,在person以外的地方沒法訪問其內部變量。只有經過閉包構造的特權方法的形式來訪問。同時實現了私有變量和訪問私有變量的特權方法的封裝:

 1 var person=function(){
 2     var name="deng";
 3     return{
 4         getName:function(){
 5             return name;
 6         },
 7         setName:function(newName){
 8             name=newName;
 9         }
10     }
11 }();
12 alert(person.name);//undefined
13 alert(person.getName());//deng
14 person.setName("jack");
15 alert(person.getName());//jack
使用閉包的注意點

1)因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。

2)閉包會在父函數外部,改變父函數內部變量的值。實現面向對象封裝中特權方法時,慎重修改私有變量。

文章在上綱上線的同時,加入了不少本身的理解。也許存在紕漏,望探討。

相關文章
相關標籤/搜索