5

さて、この質問のタイトルの言い方がわかりません。

openDir = (path) ->
socket.emit "get_metadata", path, (data) ->
    columnBox = $ "<div/>", class: "columnbox"
    for item in data.contents
        itemBox = $ "<div/>", class: "itembox"
        itemBox.click ->
            columnBox_inner.children().removeClass "selected"
            itemBox.addClass "selected" # <<<--- Over here
            openDir item.path
        columnBox.append itemBox
    columnBox.appendTo "#columnscontainer"

変数がのスコープ here のitemBox下で定義されていることを理解しています。openDirしかし、指摘された行はラムダ関数内にあるため、親スコープによって参照される最後のオブジェクトに変更されるのではなく、親スコープitemBoxによって参照されるオブジェクトをキャプチャするべきではありませんか?itemBox

わかりやすく言うと、それぞれのクリック ハンドラーがそれ自体itemBoxに対して実行addClass "selected"されることを期待しています。しかし、何が起こるかというとitemBox、各クリック ハンドラーで常に最後の itemBox が参照されます。

itemBox が宣言される場所を変更することで、これを簡単に修正できます。つまり、変化する

for item in data.contents

の中へ

data.contents.forEach (item) ->

しかし、ラムダ関数が変数の現在の値をキャプチャしない理由を知りたいです。

4

1 に答える 1

9

このループ:

for item in data.contents
    itemBox = $ "<div/>", class: "itembox"

(Coffee|Java)Script スコープに慣れていない場合は、やや誤解を招く可能性があります。スコーピングは実際には次のようになります。

itemBox = undefined
for item in data.contents
    itemBox = $ "<div/>", class: "itembox"

そのため、変数は 1 つしかなくitemBox、その同じ変数がループの各反復で使用されます。クリック ハンドラーは変数への参照を保持しますitemBoxが、クリック ハンドラーが呼び出されるまで変数を評価しないため、すべてのハンドラーが同じitemBox値になりitemBox、ループの最後の値になります。

細かいマニュアルから:

JavaScript ループを使用して関数を生成する場合、ループ変数が確実に閉じられるようにするためにクロージャー ラッパーを挿入するのが一般的であり、生成されたすべての関数が最終的な値を共有するだけではありません。CoffeeScript はdoキーワードを提供します。このキーワードは、渡された関数をすぐに呼び出し、引数を転送します。

だからあなたはこれを行うことができます:

for item in data.contents
    do (item) ->
        # As before...

itemBoxループの各反復に個別にスコープを設定します。

使用forEach:

data.contents.forEach (item) ->

関数をループの本体として効果的に使用しており、その関数内の変数はその関数にスコープされるため、単純なループの代わりに機能します。

于 2012-08-16T22:32:11.863 に答える