來源:http://moonbase.rydia.net/mental/blog/programming/the-biggest-mistake-everyone-makes-with-closures.html
看下面的Ruby代碼
k = []
for x in 1..3
k.push(lambda { x })
end
for x in 1..3
k.push(lambda { x })
end
執(zhí)行
k[0].call
你可能預(yù)期返回1,實(shí)際的結(jié)果卻是3。這是為何?這是因?yàn)樵?strong>迭代過程中共用了同一個(gè)context,導(dǎo)致k中的三個(gè)閉包都引用了同一個(gè)變量x。不僅僅Ruby有這個(gè)問題,python也一樣
k = [lambda: x for x in xrange(1, 4)]
k[0]()
k[0]()
Javascript同樣如此
var k = [];
for (var x = 1; x < 4; x++) {
k.push(function () { return x; });
}
alert(k[0]())
for (var x = 1; x < 4; x++) {
k.push(function () { return x; });
}
alert(k[0]())
解決這個(gè)問題很簡單,就是將閉包包裝到一個(gè)函數(shù)里,建立新的context,那么迭代過程中生成的閉包所處的context不同:
def make_value_func(value)
lambda { value }
end
k = (1..3).map { |x| make_value_func(x) }
lambda { value }
end
k = (1..3).map { |x| make_value_func(x) }
這個(gè)時(shí)候,k[0].call正確地返回1。
這個(gè)問題并非在所有支持閉包的語言里都存在,例如scheme中就沒有問題
(define k '())
(do ((x 1 (+ x 1)))
((= x 4) '())
(set! k (cons (lambda () x) k)))
(set! k (reverse k))
((car k)) =>1
(do ((x 1 (+ x 1)))
((= x 4) '())
(set! k (cons (lambda () x) k)))
(set! k (reverse k))
((car k)) =>1
Erlang也沒有問題
K=[ fun()->X end || X <- [1,2,3]].
lists:map(fun(F)-> F() end,K).
lists:map(fun(F)-> F() end,K).
再試試Clojure:
(def k (for [i (range 1 4)] (fn [] i)))
(map #(%) k)
(map #(%) k)
同樣沒有問題。這里Erlang和Clojure都采用列表推斷。