莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          閉包使用的一個陷阱

          Posted on 2010-07-09 23:52 dennis 閱讀(1744) 評論(4)  編輯  收藏 所屬分類: 動態(tài)語言

          來源:http://moonbase.rydia.net/mental/blog/programming/the-biggest-mistake-everyone-makes-with-closures.html

          看下面的Ruby代碼
          = []
          for x in 1..3
            k.push(
          lambda { x })
          end

          執(zhí)行
          k[0].call

          你可能預(yù)期返回1,實際的結(jié)果卻是3。這是為何?這是因為在迭代過程中共用了同一個context,導(dǎo)致k中的三個閉包都引用了同一個變量x。不僅僅Ruby有這個問題,python也一樣
          = [lambda: x for x in xrange(14)]
          k[0]()

          Javascript同樣如此

          var k = [];
          for (var x = 1; x < 4; x++) {
            k.push(function () { 
          return x; });
          }
          alert(k[0]())


          解決這個問題很簡單,就是將閉包包裝到一個函數(shù)里,建立新的context,那么迭代過程中生成的閉包所處的context不同:
          def make_value_func(value)
            
          lambda { value }
          end
          = (1..3).map { |x| make_value_func(x) }

          這個時候,k[0].call正確地返回1。

          這個問題并非在所有支持閉包的語言里都存在,例如scheme中就沒有問題

          (define k '())
          (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).

          再試試Clojure:
          (def k (for [i (range 1 4)] (fn [] i)))
          (map #(
          %) k)

          同樣沒有問題。這里Erlang和Clojure都采用列表推斷。





          評論

          # re: 閉包使用的一個陷阱  回復(fù)  更多評論   

          2010-07-10 00:40 by Rain Yang
          Ruby 1.9.x 不會再這樣了。

          # re: 閉包使用的一個陷阱[未登錄]  回復(fù)  更多評論   

          2010-07-15 14:46 by sin
          actionscript同樣問題

          # re: 閉包使用的一個陷阱  回復(fù)  更多評論   

          2012-12-22 15:57 by Zhiqiang.Zhan
          這不是閉包的缺陷,這是因為Ruby,Python還有JavaScript這些支持函數(shù)式等編程語言中的block都不能形成閉包作用域,而你拿來對比的Erlang和Clojure的例子很不恰當(dāng),它的等價形式相當(dāng)于Ruby中的
          k = []
          (1..3).each do |x|
          k.push(lambda {x})
          end

          puts k[0].call

          而這個剛好是OK的。
          這是因為do...end block形成了閉包,但是for ...end中的語句沒有形成新的閉包。

          # re: 閉包使用的一個陷阱  回復(fù)  更多評論   

          2013-11-04 02:45 by 我傻逼我自豪
          js例:
          var a = [];
          for (var x = 4; x >= 0; x--) {
          a.push(function (x) {
          return x;
          });
          }
          alert(a[4]());
          主站蜘蛛池模板: 黔西县| 和田市| 弋阳县| 无极县| 崇礼县| 肇庆市| 延边| 张家口市| 洞口县| 余庆县| 天气| 榆树市| 郴州市| 周口市| 罗山县| 浦城县| 封丘县| 定远县| 桦川县| 巨鹿县| 宽城| 五华县| 婺源县| 河北省| 西宁市| 襄城县| 武安市| 固始县| 城市| 樟树市| 称多县| 南平市| 北海市| 灵石县| 洛宁县| 巫溪县| 红河县| 赤城县| 松江区| 巨野县| 长沙市|