LoginSignup
14
14

More than 5 years have passed since last update.

いつか絶対に引っかかるというJavaScriptの予言が的中した

Last updated at Posted at 2015-08-20

「ん? 変数の値が上書きされてる!」

「あれー。なんかループの中のデータがおかしい!」とか、「ん?変数の値が上書きされてる!」とかいう事態が起きたら、この投稿を思い出してください。

予言しよう!あなたはいつか、この罠を踏む。
いつかあなたが絶対に引っかかる、ある一つのJavaScriptの罠 - Qiita

ついに引っかかってしまった。

class Trap
  constructor: ->
    @prefix = '#btn-'
    @types = ['hoge', 'hage', 'fuge']
    @bind @types

  bind: (types) ->
    for type in types
      $(@prefix + type).click =>
        @setType(type)

  setType: (type) ->
    console.log "Set #{type}."

これを実行すると、どのボタンをクリックしても結果がSet fuge.になってしまう。

よくある失敗の例だ。ループの中で関数を定義しているわけだが、ループ変数は関数の外部で定義されている。クリックしたときに実行する関数は、実行時に i を参照するが、それはループが終わったときの値だ。従って、今回の例の場合は3が表示される。
JavaScript のクロージャとループ、変数について | UB Lab.

回避方法

ループ内で関数を定義していて、ループ変数の値を表示するには、関数を戻す関数を定義する必要がある。下記の例では、クリックしたときに実行される関数は、関数を戻しており、かつ、関数自体を呼び出すように指定して引数にループ変数を渡している。console.log で渡している引数は、最初の関数の引数と同じものを参照するので、ループしている間の i の値を表示することができる。

for(var i = 0; i < array.length; i++) {
    array[i].click((function(n) {
        return function() {
            console.log(n);
        }
    }).call(this, i));
}

JavaScript のクロージャとループ、変数について | UB Lab.

これCoffeeScriptでどうやって書くの?

2015-08-24 更新

bind: (types) ->
  for type in types
    do (type) =>
      $(@prefix + type).click => 
        @setType(type)

@pomutemuさんがコメントで綺麗な書き方を教えてくださったので、内容を更新しました! codepenライブデモ


以下旧版
デザイナー崩れの脳味噌では上手く書けなかったので便利なツールに頼ることにした。

bind: (types) ->
  for type in types
    $(@prefix + type).click ((t) =>
      =>
        @setType t
    ).call @, type

これで無事、ループ内の変数をコールバックの引数に渡すことができた。

14
14
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
14