問題
代碼A
function fun(n,o){ console.log(o); return { fun:function(m){//[2] return fun(m,n);//[1] } } } var a=fun(0); a.fun(1); a.fun(2); a.fun(3); var b=fun(0).fun(1).fun(2).fun(3); var c=fun(0).fun(1); c.fun(2); c.fun(3);
求出程式輸出
這是一個閉包測試題
轉換為等價碼
return返回的對象的fun屬性對應一個新建的函數對象,這個函數對象將形成一個閉包作用域,使其能夠訪問外層函數的變量n及外層函數fun,為了不將fun函數和fun屬性搞混,我們將上述程式碼修改如下:
代碼B
function _fun_(n,o){ console.log(o); return { fun:function(m){ return _fun_(m,n); } } } var a=_fun_(0);//undefined a.fun(1);//0 a.fun(2);//0 a.fun(3);//0 var b=_fun_(0).fun(1).fun(2).fun(3); //undefined,0,1,2 var c=fun(0).fun(1);//undefined,0, c.fun(2);//1 c.fun(3); //1
那麼就有同學問了,為什麼可以這樣改呢,你怎麼能確定[1]處的fun不是[2]代碼所在處的fun呢,要知道此處的fun屬性可是指向一個函數對象哦~
這裡就要說到JS的詞法作用域,JS變數作用域存在於函數體中即函數體,且變數的作用域是在函數定義宣告的時候就是確定的,而非在函數運行時。
如下碼
var name="global"; function foo(){ console.log(name); } function fooOuter1(){ var name="local"; foo(); } fooOuter1();//输出global 而不是local,并且和闭包没有任何关系 function fooOuter2(){ var name="local"; function foo(){ console.log(name); } foo(); } fooOuter2();//输出local 而不是global,在函数声明是name变量作用域就在其外层函数中,嗯嗯就是闭包~<br />
好了我們回到題目,在函數聲明定義階段,[2]處的匿名函數進行定義聲明,發現在[1]處需要引用一個名為fun的函數對象,那麼首先在當前函數體內尋找,發現沒有,那麼就到其外層函數-這個匿名函數的包裹函數中去查找,發現也沒有,到外層函數中去,發現外面沒有函數包裹了,那就到全局環境下去找,額偶終於找到了......就把fun函數指定為全域環境下的fun函數物件並加入到匿名函數的閉包中去。至此我們就知道代碼B為什麼和代碼A是等價的了~~~
建立閉包作用域
JS在詞法分析結束後,確定了1個閉包,就是傳回的物件fun屬性對應的匿名函數的閉包-存取全域環境下的_func_及其外層函數的函數內部變數n;
在每次_func_執行的時候,都會將閉包中變數的作用域資訊傳遞到函數執行環境中,供函數執行時取得變數值時使用
執行輸出
var a=_fun_(0);//undefined a.fun(1);//0 a.fun(2);//0 a.fun(3);//0
_fun_函數執行,因為第2個參數未定義,輸出undefined。然後返回一個對象,帶有fun屬性,指向一個函數對象-帶有閉包,能夠訪問到_fun_和變量n_
a.fun(1)執行傳回的物件的fun方法,傳入m的值1,呼叫回傳_fun_(1,0)
所以輸出為0,a.fun(2),a.fun(3)和a.fun(1)
var b=_fun_(0).fun(1).fun(2).fun(3);
等價代碼:
var b=_fun_(0);
var b1=b.fun(1);
var b2=b1.fun(2);//[3]
var b3=b2.fun(3);//[4]
前2句和上面的輸出相同undefined,0,當[3]被呼叫時,b1物件中有一個閉包,引用了_fun_函數及外層函數變數n=1,所以匿名函數執行的函數呼叫為_fun_(2,1),輸出結果為1,並傳回一個新的物件。
當[4]執行時,b2物件也有一個閉包,引用了_fun_函數及外層函數變數n=2,執行_fun_(3,2),輸出結果為2
var c=fun(0).fun(1);//undefined,0, c.fun(2);//1 c.fun(3); //1
能看懂前面的程式碼執行解釋,理解上面的程式碼執行輸出就不會有問題了,希望大家喜歡。