1. 程式人生 > >簡單說 一道JS閉包面試題

簡單說 一道JS閉包面試題

說明

最近看到這樣一段程式碼

 function fun(n,o){
    console.log(o);
    return {
        fun:function(m){
            return fun(m,n);
        }
    };
 }

 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);
 //問:三行a,b,c的輸出分別是什麼?

覺得有點意思,和大家一起來聊聊。
我相信如果你不是非常理解JavaScript中的閉包,一定是不想看這段程式碼的。

解釋

好的,我們暫時先不去想這段程式碼,先看點簡單的

function fun0(){
    var a=1;
    console.log(a);
}
function fun1(){
    console.log(a);
}
fun0(); //1
fun1(); //報錯 a is not defined

這段程式碼,我相信大家應該知道最後為什麼結果會是 1 和 報錯 的,在函式內宣告的變數只在函式體內定義,它們是區域性變數,作用域是區域性的,所以 函式 fun1 呼叫後,找不到a,就報錯了,JavaScript採用詞法作用域,函式的執行依賴於變數作用域,這個作用域是在函式定義時決定的,所以我們只要改改上面函式 fun1的位置,它就不會報錯了。

function fun0(){
    var a=1;
    console.log(a);

    //把fun1放在fun0中,就不報錯了
    function fun1(){
        console.log(a);
    }
    fun1();   //1
}
fun0();   //1

程式碼改成這樣,只是把fun1 放在 fun0 中就不報錯了,函式呼叫後都輸出1

好了,我們來看最開始提到的程式碼,先簡化一下

function fun(n,o){
    return {
    }
}

我們先看這段程式碼,fun 呼叫後會怎麼樣?
很明顯會返回一個空物件,記住,fun呼叫後會返回物件,這點很重要。

 function fun(n,o){
    console.log(o);
    return {
        fun:function(m){
            return fun(m,n);
        }
    };
 }

 var a = fun(0);

這裡提一句,當呼叫函式的時候傳入的實參比函式宣告時指定的形參個數要少,剩下的形參都將設定為undefined值。
console.log(o); 輸出undefined
var a = fun(0); 那a是值是什麼,是fun(0),返回的那個物件

{
    fun:function(m){
        return fun(m,0);
    }
}

這個物件,有一個fun的方法,方法返回的結果就是最外面 fun 呼叫的結果。

這裡寫圖片描述
var a=fun(0),傳入一個引數0,那就是說,函式fun中引數 n 的值是0了,而返回的那個物件中,需要一個引數n,而這個物件的作用域中沒有n,它就繼續沿著作用域向上一級的作用域中尋找n,最後在函式fun中找到了n,n的值是0,這段話是本文的重點, 明白這段,那問題就容易解決了。

說到這裡,這道題基本上可以解決了,希望大家能聽明白我上面說的話,下面的就簡單了。我們一步一步看。

現在我們知道 a 是

{
    fun:function(m){
        return fun(m,0);
    }
}

這樣的一個物件
a.fun(1); 會怎麼樣?看程式碼

{
    fun:function(1){
        return fun(1,0);
    }
}

a.fun(1); 返回的結果,就是 fun(1,0),返回的結果

 function fun(n,o){ //n的值為1,o的值為0
        console.log(o);
        return {
            fun:function(m){
                return fun(m,n);//n的值為1
            }
        };
}
fun(1,0);  //輸出0,並返回一個物件,這個物件有一個fun的方法,這個方法呼叫後,會返回外層fun函式呼叫的結果,並且外層函式的第二個引數是 n 的值,也就是1  

a.fun(2); 會怎麼樣?看程式碼

{
    fun:function(2){
        return fun(2,0);
    }
}

a.fun(2); 返回的結果,就是 fun(2,0),返回的結果

 function fun(n,o){ //n的值為2,o的值為0
        console.log(o);
        return {
            fun:function(m){
                return fun(m,n); //n的值為2
            }
        };
}
fun(2,0); //輸出0,並返回一個物件,這個物件有一個fun的方法,這個方法呼叫後,會返回外層fun函式呼叫的結果,並且外層函式的第二個引數是 n 的值,也就是2  

a.fun(3); 就不說了,一樣的。

var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);

我們繼續說b,b和a的不同在於,var a = fun(0); 之後一直用的是a這個物件,是同一個物件,而b每次用的都是上次返回的物件。
如果改成這樣

var a = fun(0); a=a.fun(1); a=a.fun(2); a=a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);

把返回的物件,重新賦值給a,這樣兩行的結果就是一樣的了。
var c = fun(0).fun(1); c.fun(2); c.fun(3);
c 與他們的不同,只是var c = fun(0).fun(1); 之後用的是同一個物件罷了。

總結

說下結果

var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
//undefined 0 0 0 

var b = fun(0).fun(1).fun(2).fun(3);
//undefined 0 1 2

var c = fun(0).fun(1); c.fun(2); c.fun(3);
//undefined 0 1 1

這篇文章只是針對這道題講了講,沒有非常著重的去講閉包這個概念,所以如果朋友們,對閉包詳細的概念還不是很理解,要趕緊學習了。
順便推薦幾篇講解閉包的文章

這裡寫圖片描述