談談自己對js閉包,執行上下文,作用域鏈,活動對象AO,變量對象VO的理解


引子:關於閉包
什么是閉包呢?
  從定義上來看,所有的函數都可以是閉包。當一個函數在調用時,引用了不是自己作用域內定義的變量(通常稱其為自由變量),則形成了閉包;閉包是代碼塊和創建該代碼塊的上下文中數據的結合


例子:   function mytest( ){                     
           var test=10;
          return function( ){ 
                 test++;
               alert(test);
      } 
}
var atest = new mytest( ); //引用返回的函數
atest( );  // 11
atest( ); //  12
ps:注意運用閉包的常見錯誤-->for(var i,len=xx.length;i<len;i++)循環;當我們所引用的自由變量為i時,由於i一直在內存中沒有釋放,所以函數每次alert(i)時,其值均為最終的i;(例子很多,就不寫了)
    通過測試結果我們可以發現, 閉包會使該函數引用的變量一直在內存中,原因是什么呢?
簡單的解釋就是因為這個返回的函數引用了變量test;當瀏覽器解析到var atest=new mytest();這一行且mytes()函數執行完畢,准備內存釋放時,發現所返回的函數引用了test變量。從而該出棧的並沒有出去;
    想完全弄清楚這個問題,我們還需要進一步理解AO(活動對象)和VO(變量對象)以及作用域鏈、執行上下文的問題。
    那么這整個機制瀏覽器到底是怎么解析的呢?
    不急,我們來看看執行上下文、作用域鏈、活動對象和變量對象;
寫在前面的執行上下文:
 a:定義:

  每次當控制器轉到ECMAScript可執行代碼的時候,即會進入到一個執行上下文。執行上下文(簡稱-EC)是ECMA-262標准里的一個抽象概念,用於同可執行代碼(executable code)概念進行區分。
活動的執行上下文組在邏輯上組成一個堆棧。堆棧底部永遠都是全局上下文(global context),而頂部就是當前(活動的)執行上下文。堆棧在EC類型進入和退出上下文的時候被修改(進棧或出棧)。
b:若我們定義執行上下文堆棧是一個數組:
ECStack = [];
//GlobalContext始終在堆棧底部,其余的FunctionContext按激活順序被壓入,結束時被彈出
ECStack = [
  globalContext
];
ECStack = [  FunctionAaContext,//函數A內部函數a可執行代碼 
FunctionAContext,//函數A可執行代碼(不包含內部函數代碼,只能對其聲明,就像在全局中只能對函數A聲明一樣,需要內部函數激活時,創建   GlobalContext                                                     出新的執行上下文環境,才能執行相應的代碼)      調用結束后出棧  
];

 

   ps:
每次進入function (即使function被遞歸調用或作為構造函數) 的時候或者內置的eval函數工作的時候,當前的執行上下文都會被壓入堆棧;

 

 

1、作用域鏈
     首先,我們如何創建一個作用域呢,function()。函數是javascript中唯一一個能創建出作用域的,也就是說for、if、while的{}是不能創建出作用域的。區別c++中的塊作用域{}。
     一個函數的作用域創建后,將貫穿他的始“{”,終“}”,作用域

在函數創建時被存儲,與函數共存亡

。這句話就應該着重理解貫穿2字了,若函數內部嵌套着多個函數,那么從最內層函數作用域依次往外就形成了作用鏈。
ps:需要我們理解作用域鏈的變量查找機制是由內往外的。先找自身作用域,再一次往外,若沒有,則等同沒有var時的聲明(為全局添加了一個屬性);

作用域鏈正是內部上下文所有變量對象(包括父變量對象)的列表


2、變量對象(Variable Object)、活動對象(Activation Object)
     瀏覽器在對代碼進行解析時,會先進行變量聲明和函數聲明以及函數形參聲明;
(全局作用域下)那么這些優先聲明的變量儲存在哪里呢? 
沒錯,就在變量對象中(抽象概念);活動對象怎么理解呢?

     抽象變量對象VO (變量初始化過程的一般行為)
    
      --> 全局上下文變量對象GlobalContextVO
          (VO === this === global)

      --> 函數上下文變量對象FunctionContextVO
           (VO === AO, 並且添加了<arguments>(形參類數組)和<formal parameters>(形參的值))

ps:在函數執行上下文中,VO是不能直接訪問的,此時由活動對象扮演VO的角色。Arguments對象是活動對象的一個屬性,它包括如下屬性:

  1. callee — 指向當前函數的引用

  2. length — 真正傳遞的參數個數

  3. properties-indexes (字符串類型的整數) 屬性的值就是函數的參數值(按參數列表從左到右排列)。 properties-indexes內部元素的個數等於arguments.length. properties-indexes 的值和實際傳遞進來的參數之間是共享的。

 

 

現在讓我們來串一下

 

1、全局執行上下文

創建global.VO

2、全局變量的賦值 | 調用函數()(激活)

激活函數后,會得到當前的AO,其中有內部函數的聲明、內部變量的聲明、形參

3、進入所激活的函數的上下文

進行所在作用域鏈上的變量的賦值 各種運算 (作用域鏈包含全局的VO,和當前執行上下文的AO)

4.a、若在函數中有內部函數調用(或自執行),重復3;

4.b  若返回一個函數(或其引用),且該函數有對自由變量的引用-->形成閉包-->作用域鏈機制依然有效-->當前已壓入執行上下文堆棧的FunctionContext不會出棧;-->回到2;

4.c  正常return或正常結束,FunctionContext出棧;-->回到2;

5.所有代碼執行完畢,程序關閉,釋放內存。

 

懇請大牛老鳥指出錯誤和理解不對的地方;大二學生一枚,努力學習中。。。   見諒見諒

純手打,轉載收藏不用注明出處,(估計也不會有,請忽略)。

 

寫在最后:

  如果你是剛開始接觸javascript,如果你也相信博客園能夠讓你的學習更加規划與深入,我推薦你看看王福鵬老師和湯姆大叔的博文,自己仔細揣摩,記錄下自己的感想,一定會有收獲的。加油。我正在努力看深入系列的設計模式,感覺看着不是很順,有些不好理解,希望有經驗的博主們能指點指點。這是我的第一篇博文,真心希望能夠在博客園中尋到良師益友。

另貼上湯姆大叔深入javascript系列博文的地址,

http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM