JavaScript---閉包和做用域鏈

做用域和做用域鏈:javascript

  參考文章 :http://www.cnblogs.com/malinlin/p/6028842.html    html

        http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html前端

        http://www.zhangyunling.com/?p=134java

        https://segmentfault.com/a/1190000000652891segmentfault

總結: 閉包

  ① js中到處是對象函數

  ②函數執行時會建立一個執行環境和變量對象性能

  ③代碼在執行環境中運行 變量對象會按照順序存到做用域鏈中this

  ④執行一次函數就會建立一個新的活動對象,就會有新的做用域鏈,多個做用域鏈互不干擾.spa

  ⑤引用函數不消失,活動變量就一直存在 , 閉包使用完了以後 將引用變量指向null,釋放內存.

1. 全局做用域(Global Scope)
  (1)最外層函數和在最外層函數外面定義的變量擁有全局做用域
  (2)全部沒有定義直接賦值的變量,自動聲明爲擁有全局做用域
  (3)全部window對象的屬性擁有全局做用域,例如window.name、window.location  以下:定時器裏指向的是全局的函數

    setTimeout("C()",1000)==setTimeout("this.C()",1000)

  

 1 var a = 1;
 2 function B(){
 3     var a = 2;
 4     setTimeout("C()",1000);
 5     setTimeout(C,2000);
 6     function C(){
 7         alert("a="+a);
 8     }
 9 }
10 function C(){
11     alert("a="+a);
12 }
13 B();

 


2. 局部做用域(Local Scope) 
  局部做用域通常只在固定的代碼片斷內可訪問到,最多見的例如函數內部

1. 執行環境和活動對象

  函數在執行時 生成執行環境變量對象 ,當代碼在一個執行環境中執行時,會建立變量對象的一個做用域鏈(scope chain)

  執行環境(execution context)定義了變量或者函數有權訪問的其餘數據,每一個執行環境都有一個與之關聯的變量對象(variable object),執行環境中定義的變量和函數就保存在這個變量對象中;
  全局執行環境是最外圍的一個執行環境,一般被認爲是window對象
  執行環境中的全部代碼執行完之後,執行環境被銷燬,保存在其中的變量和函數也隨之銷燬;(全局執行環境到應用退出時銷燬).

  在閉包,每次執行A函數時,都會生成一個A的活動變量和執行環境,執行完畢之後,A的執行環境銷燬,可是活動對象因爲被閉包函數引用,因此仍然保留,因此閉包使用完了以後 將引用變量指向null


3. 做用域鏈(Scope Chain)
   當代碼在一個執行環境中執行時,會建立變量對象的一個做用域鏈(scope chain),做用域鏈用來指定執行環境有權訪問的全部變量和函數的訪問順序;當一個函數建立後,它的做用域鏈會被建立此函數的做用域中可訪問的數據對象填充。

  做用域鏈的最前端,始終是當前代碼執行環境的變量對象,若是這個環境是函數,則其活動對象就是變量對象
  做用域鏈的下一個變量對象,來自外部包含環境,再下一個變量對象,來自下一個外部包含環境,以此類推直到全局執行環境
  在函數執行過程,根據當前執行環境的做用域鏈來逐層向外查找變量,而且進行標識符解析

  這些值按照它們出如今函數中的順序被複制到運行期上下文的做用域鏈中。 它們共同組成了一個新的對象,叫「活動對象(activation object)」,該對象 包含了函數的全部局部變量、命名參數、參數集合以及this,而後此對象會 被推入做用域鏈的前端,當運行期上下文被銷燬,活動對象也隨之銷燬。

   在函數執行過程當中,沒遇到一個變量,都會經歷一次標識符解析過程以決定從哪裏獲取和存儲數據。該過程從做用域鏈頭部,也就是從活動對象開始搜索,查找同名的標識符,若是找到了就使用這個標識符對應的變量,若是沒找到繼續搜索做用域鏈中的下一個對象,若是搜索完全部對象都未找到,則認爲該標識符未定義。函數執行過程當中,每一個標識符都要經歷這樣的搜索過程。從做用域鏈的結構能夠看出,在運行期上下文的做用域鏈中,標識符所在的位置越深,讀寫速度就會越慢。在編寫代碼的時候應儘可能少使用全局變量,儘量使用局部變量。一個好的經驗法則是:若是一個跨做用域的對象被引用了一次以上,則先把它存儲到局部變量裏再使用。

閉包:

  1.閉包能夠訪問函數中的變量。

  2.可使變量長期保存在內存中,生命週期比較長。但閉包不能濫用,不然會致使內存泄露,影響網頁的性能。閉包使用完了後,要當即使用資源,將引用變量指向null。

<script>
    function A(){
        var x = 1;
        return function(){
            x++;
            console.log(x);
        }
    }
    var m1 = A();//第一次執行A函數
    m1();//2
    m1();//3
    var m2 = A();//第二次執行A函數
    m2();//2
    m1();//4
</script>

1.(爲何連續執行m1的時候,x的值在遞增?)
answer:由於m1在引用的活動對象A一直沒有釋放(想釋放的話可讓m1=null),因此x的值一直遞增。
2.定義函數m2的時候,爲何x的值從新從1開始了?
answer:由於又一次運行了A函數,生成一個新的A的活動對象,因此m2的做用域鏈引用的是一個新的x值。
3.m1和m2裏面的x爲何是相互獨立,各自維持的?
answer:由於在定義m1和m2的時候,分別運行了A函數,生成了兩個活動對象,因此,m1和m2的做用域鏈是指向不一樣的A的活動對象的。

好的,到這裏先回顧一下前面說到的知識點:

  執行環境和變量對象在運行函數時生成
  執行環境中的全部代碼執行完之後,執行環境被銷燬,保存在其中的變量和函數也隨之銷燬;(全局執行環境到應用退出時銷燬) 

 

閉包常見題目:

  

 1 function f1(){
 2     var n=999;
 3     nAdd=function(){n+=1}
 4     function f2(){
 5       alert(n);
 6     }
 7     return f2;
 8   }
 9   var result=f1();
10   result(); // 999
11   nAdd();   //首先在nAdd前面沒有使用var關鍵字,所以nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個匿名函數自己也是一個閉包,
12   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,能夠在函數外部對函數內部的局部變量進行操做。

 

 

閉包中this的指向:

  

var name = "The Window";
var object = {
    name : "My Object",
    
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};

document.write(object.getNameFunc()());//The Window  匿名函數的執行環境具備全局性,所以其this對象一般指向window
相關文章
相關標籤/搜索