莊周夢蝶

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

          閉包使用的一個陷阱

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

          來源: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

          執行
          k[0].call

          你可能預期返回1,實際的結果卻是3。這是為何?這是因為在迭代過程中共用了同一個context,導致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]())


          解決這個問題很簡單,就是將閉包包裝到一個函數里,建立新的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: 閉包使用的一個陷阱  回復  更多評論   

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

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

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

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

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

          puts k[0].call

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

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

          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]());
          主站蜘蛛池模板: 蒙阴县| 红原县| 木里| 渑池县| 兴国县| 嘉祥县| 汕头市| 绥江县| 兴化市| 嘉禾县| 富源县| 凌海市| 长汀县| 永春县| 从化市| 竹山县| 彝良县| 会泽县| 和田市| 赤水市| 石棉县| 甘洛县| 团风县| 长海县| 绍兴市| 孙吴县| 米林县| 始兴县| 乌兰察布市| 巴林左旗| 宝坻区| 丹寨县| 东山县| 临夏市| 崇明县| 白玉县| 澳门| 昭觉县| 蓝山县| 丹凤县| 宽城|