引例
第一次在公衆號上看到一道面試題,最近學習javascript又看到廖大大的類似的例子,覺得有必要記錄一下。
下面這道python題,大家看看會輸出什麼:
def testFun():
temp = [lambda x: i*x for i in range(4)]
return temp
for everyLambda in testFun():
print(everyLambda(2))
心裏默默想了一下,嗯,我以爲是: 0 2 4 6
正確答案是: 不妨打開編輯器調敲哈代碼
6 6 6 6
原因是:閉包變量的遲綁定,在調用時才真正計算閉包中所引用的外部變量,也就是i的值。。。將上面由列表生成式與匿名函數構成的閉包改寫下,就比較好看明白了。。。
def testFun():
r = []
for i in range(4):
def f(x):
return i*x
r.append(f)
return r
看了阮一峯大神的blog後, 我的理解:testFun()調用時,列表中所有元素生成,r=[f1, f2, f3,f4],每個函數對象均爲閉包,只是其引用的外部函數的i值現在未確定,而testFun()調用結束,此時i的值爲3,且存在了內存中。。。在for in 後取出f1 且everyLambda(2) 調用時,將i=3 x=2代入,最後得到結果6, 後面函數同理得結果
如何得到 0 2 4 6
第一種: 還是閉包思想,通過使用默認參數立即綁定它的參數
def testFun():
temp = [lambda x, i=i: i*x for i in range(4)]
return temp
for everyLambda in testFun():
print(everyLambda(2))
第二種:利用偏函數,將函數的某些參數固定住,對這個題來說,剛好可以把乘法的一個乘數固定,我覺得有點取巧,但是偏函數這個思想,非常好
from functools import partial
from operator import mul
def testFun():
temp = [partial(mul, i) for i in range(4)]
return temp
for everyLambda in testFun():
print(everyLambda(2))
第三種:優雅的寫法,直接用生成器(生成式表達式)
def testFun():
return (lambda x: i*x for i in range(4))
for everyLambda in testFun():
print(everyLambda(2))
第四種:還是生成器思想,只是用函數生成器,個人最喜歡這個,覺得結構思路最清晰
def testFun():
for i in range(4):
yield lambda x: i*x
for everyLambda in testFun():
print(everyLambda(2))
Javascript引例
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
在上面的例子中,每次循環,都創建了一個新的函數,然後,把創建的3個函數都添加到一個Array中返回了。
你可能認爲調用f1(),f2()和f3()結果應該是1,4,9,但實際結果是:
f1(); // 16
f2(); // 16
f3(); // 16
全部都是16!原因就在於返回的函數引用了變量i,但它並非立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了4,因此最終結果爲16。
返回閉包時牢記的一點就是:返回函數不要引用任何循環變量,或者後續會發生變化的變量。
如何得到 1 4 9
第一種:如果一定要引用循環變量怎麼辦?方法是再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量後續如何更改,已綁定到函數參數的值不變:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
注意這裏用了一個“創建一個匿名函數並立刻執行”的語法:
(function (x) {
return x * x;
})(3); // 9
這部分摘自廖大大的閉包
第二種:喜歡上了python中生成器的寫法,而實際javascript生成器的思想也來自於python,那用生成器思想改寫下, js中箭頭函數是與python中匿名函數類似的
function* count() {
for (var i=1; i<=3; i++) {
yield (()=>(i*i));
}
}
for (var item of count()){
console.log(item());
}
最後: 關於閉包的相關知識,下面有阮老師的鏈接,有寫得錯誤的地方,歡迎指出,一起學習,纔不孤單
參考文檔:
學習Javascript閉包(Closure)