前言
今天逛技術公衆號的時候看到一個大佬分享的關於閉包的微信筆試題,自己嘗試做了一下,發現還挺有趣的,在此記錄一下。
題目
function test(a,b) {
console.log(b)
return {
test:function(c){
return test(c,a);
}
};
}
var retA = test(0);
retA.test(2);
retA.test(4);
retA.test(8);
var retB = test(0).test(2).test(4).test(8);
var retC = test('good').test('bad');
retC.test('good');
retC.test('bad');
思路
這是一個很有趣的閉包問題。其實只要談到閉包,第一反應就是【儲存變量】。所以需要思考的就是什麼情況下儲存了變量,存了哪些變量。
以下分爲三個部分來解析。
一、retA 部分
-
var retA = test(0)
結果:undefined
解析:這個比較簡單,調用了
test(0)
。傳了參數 a 爲 0,參數 b 沒傳,所以打印 undefined。返回值:對象
{test:function(c){return test(c,a); }}
。儲存值:作用域內儲存了
a=0,b=undefined
。 -
retA.test(2)
結果:0
解析:調用對象
{test:function(c){return test(c,a); }}
內的test(2)
,此時函數內參數 c 爲 2。a 爲上次作用於儲存值 0。由於又返回了 test (return test(c,a)
)。所以又打印 b (console.log(b)
)。此時的 b 就是傳過去的 a。所以打印 0。 -
繼續執行
retA.test(4); retA.test(8);
與retA.test(2)
一樣,retA
作用域內儲存的還是a=0,b=undefined
。所以console.log(b)
打印的都是儲存的 a ,都爲 0。
二、retB 部分
-
var retB = test(0)
結果:undefined
解析:這個跟 retA 部分的 1 一樣,就不贅述了。
返回值:對象
{test:function(c){return test(c,a); }}
。儲存值:作用域內儲存了
a=0,b=undefined
。 -
.test(2)
結果:0
解析:由於調用了
test(0)
之後返回值是{test:function(c){return test(c,a); }}
。那麼跟 retA 部分的 2 一樣,console.log(b)
就是 0 。返回值:對象
{test:function(c){return test(c,a); }}
。儲存值:注意這時候作用域內儲存的就是
a=2,b=0
。test(c,a)
中的參數 c = 2,a = 0(取外層作用於存儲的值 a = 0)。到了test(a,b)
調用的時候 a 就是 2,b 就是 0 了。實參與形參的對應。 -
.test(4)
結果:2
解析:由於調用了
test(2)
之後返回值依舊是{test:function(c){return test(c,a); }}
。此時 c = 4,a = 2(參照 2.test(2)
中的儲存值)。跟 2.test(2)
中一樣test(a,b)
調用的時候 a 就是 4,b 就是 2 了。返回值:對象
{test:function(c){return test(c,a); }}
。儲存值:**注意這時候作用域內儲存的就是
a=4,b=2
。 -
.test(8)
結果:4
解析:由於調用了
test(4)
之後返回值依舊是{test:function(c){return test(c,a); }}
。此時 c = 8,a = 4(參照 2.test(4)
中的儲存值)。跟 2.test(4)
中一樣test(a,b)
調用的時候 a 就是 8,b 就是 4 了。返回值:對象
{test:function(c){return test(c,a); }}
。儲存值:作用域內儲存的就是
a=8,b=4
。
三、retC 部分
-
var retC = test('good').test('bad')
結果:undefined good
解析:這個跟前面的連調兩次一樣,一次打印是undefined,一次打印是第一次調用傳過去之後儲存的值(也就是第一次調用的參數)。
返回值:對象
{test:function(c){return test(c,a); }}
。儲存值:作用域內儲存了
a=bad,b=good
。 -
retC.test('good')
結果:bad
解析:由於前面連調了兩次
test
之後a=bad,b=good
。返回再調用test(c,a)
的時候 c = ‘good’。a = ‘bad’。傳參過去調用test(a,b)
之後,console.log(b)
就是 ‘bad’ 。返回值:對象
{test:function(c){return test(c,a); }}
。儲存值:這時候作用域內儲存的是
a=good,b=bad
。 -
retC.test('bad')
結果:`bad解析:注意這裏不是像 retB 部分一樣鏈式調用。這裏是繼續調用的 retC。所以它儲存的值應該是 1.
var retC = test('good').test('bad')
中一樣a=bad,b=good
。調用test(c,a)
的時候 c = ‘bad’。a = ‘bad’。傳參過去調用test(a,b)
之後,console.log(b)
也是 ‘bad’ 。返回值:對象
{test:function(c){return test(c,a); }}
。儲存值:作用域內儲存的就是
a=bad,b=bad
。
總結
上面有提到過閉包的核心就是【儲存變量】。要考慮的點就是什麼時候儲存了變量,儲存了哪些變量。
- retA 部分調用了
test(0)
儲存了值之後,後面retA.test(2) retA.test(4) retA.test(8)
都是使用 retA 的儲存值(a=0,b=undefined
)。所以console.log(b)
都是 0。 - retB 部分是鏈式調用。每次調用之後都魔性地偷換了 a 和 b 的值。所以每次
console.log(b)
出來之後打印的都是前面調用傳入的值。 - retC 部分就是 retA 和 retB 的結合。前面部分是鏈式調用,然後儲存值,之後分開調用,分開調用共用的都是前面鏈式調用儲存的值。