我的JavaScript之旅——“閉包”是什么時候創建的

作者: 菜阿彬  來源: 博客園  發布時間: 2010-09-19 07:26  閱讀: 1098 次  推薦: 0   原文鏈接   [收藏]  

  直接看代碼:

 
function Outer(){
var x = 1;
function Inner(y) {return x + y};
return Inner;
}

  對于這樣一個簡單的閉包函數,下面兩種調用方式有什么不一樣的地方?

 
//方式1
var inner1 = Outer();
var result = inner1(2); //3
 
//方式2
var result = Outer()(2); //3

  此篇試圖解答這個問題。先復習一下:

  上篇文章說到,每次執行一個function時,就會進入一個新的“執行上下文”(execution context)。context的幾個重要屬性:

  1, 有一個對應的variable object;在global context中就是global object。
  2, 有一個對應的scope chain,這個scope chain的第一個object就是variable object;
  3, 有一個不變的this變量。

  其中第2條,scope chain是JS實現閉包(closure)的關鍵所在,這篇對“閉包”展開描述,以加深印象。后續文章將對this專門探討。

  Variable object的實例化三部曲

  我們已經知道,當JS執行時碰到一個變量,它會到scope chain里遞歸去找,而scope chain是由variable object和global object組成的一個object chain,global object包含JS預定義好的所有object,function的variable object則會包含函數內聲明的所有東西,包括function的參數、內部函數、局部變量。創建Variable object的過程有三步,上篇有寫過,這邊是官方的文檔。簡述如下:

  1, 為variable object創建與函數參數同名的屬性;屬性值為傳入的參數值。

  2, 對于“function declaration(見下節)”,首先創建函數,然后為variable object創建屬性;屬性值即為該函數實例。覆蓋1的同名屬性。

  3, 為variable object創建各變量的屬性;屬性值為undefined。不覆蓋1,2的同名屬性。

  function declaration vs. function statement

  下面就是一個function declaration:

 
function A(){
}

  下面是一個function statement:

 
var A = function A(){
}

  這是創建函數的三種方式之二,區別在上面的三部曲中就可以看出來:

  1, 在進入execution context時(還沒執行任何代碼),function declaration就已經在第2步創建函數實例起來了;而function statement屬于第3步,而且初始值是undefined,要到執行這行代碼時,函數才會被創建起來。

  2, function statement不會覆蓋同名的function declaration。

  驗證一下:  

  如圖:A可以先用再聲明;B則不行。B在執行到最后一句之前,都是undefined。但是B這個屬性是一開始就在的:  

  (this是什么?因為上面的代碼是執行在global context中,B屬性應該創建到global object身上,也就是this。下篇會詳述this。)

  三部曲中說了,function declaration會覆蓋同名的1里的參數,所以它是varaible object里的第一等公民:  

  而function statement不會。

  

  閉包的創建時機

  對于開頭的這段代碼:

 
function Outer(){
var x = 1;
function Inner(y) {return x + y};
return Inner;
}

  然后執行 :

 
var inner1 = Outer();

  回憶一下這時會發生什么?

  會進入一個新的“執行上下文”(Outer Context),創建OuterVariableObject(有x和Inner屬性),放到OuterScopeChain的最前方。而且會創建Inner函數,創建時把當前Scope Chain作為Inner函數的[[Scope]]屬性。這是重點。

  創建起來的Inner函數被inner1變量引用,Inner有[[Scope]]屬性,引用了OuterScopeChain,即[OuterVariableObject, global object],而OuterVariableObject又引用了局部變量x。所以inner1變量就對Outer函數體內的局部變量x有間接的引用。內部函數對外部函數的變量有了引用關系——閉包就是這時產生的。每次對外部函數的調用,都會產生一次閉包。

  garbage collection

  很多人都聽過閉包容易引起內存泄露。為什么呢?因為如上所述,inner1變量對x有間接引用,而inner1是聲明在global context下的一個變量,它在global context下隨時可以被用,那么JS的垃圾回收器就不會回收它(inner1),當然也就不會回收它所引用的x——直到退出global context,也就是我們關掉網頁的時候。

  這就是文章開頭兩種調用方式的區別:方式1,x在關掉網頁前一直不能被回收;而方式2,x會被回收。

  后續文章將介紹閉包的用處,和this關鍵字(比我想象的復雜)。

 

0
0
 
標簽:JavaScript
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()