一道面試題看python與javascipt殊途同歸的閉包

引例

第一次在公衆號上看到一道面試題,最近學習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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章