

このコードを実行してみたら、不思議な結果がでました

[code]

    function printFive() {
        for (var i = 0; i < 5; i++) {
            setTimeout(function () {
                console.log(i);
            }, i * 1000);
        }
    }
    
    printFive();
    
[/code]

こうすれば、

[code]

    0
    1
    2
    3
    4
    
[/code]

がゆっくり出力されると思うと、実際は

[code]

    5
    5
    5
    5
    5
    
[/code]

がでました。

[jsfiddle](http://jsfiddle.net/964boo4x/)

どうして`0`から`4`までループしているのに、`5`ばかりが出てくるんですか。どうすれば思い通りになりますか。


CC BY-SA 3.0
2014-12-19T13:57:37.983
2152
2015-02-11T01:17:23.257
2014-12-21T06:20:24.960
4788
4788
question
javascript
どうしてこのsetTimeoutのコードが同じ値ばかり出力していますか


以上の結果は[クロージャ](http://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3)による動作です。

簡単にいうと、ある関数の実行中に出てくる変数はたとえその中の関数で使われても、結局はどこにあっても同じ変数です。

`setTimeout`の中の`i`は`setTimeout`外側の`i`と全く同じ変数です。だから、外側の`i`の値が変われば、内側の`i`の値も変わってきます。

`setTimeout`に渡している関数は`for`ループが終わったあとに実行されます。実行されたら、その時点の`i`の値が表示されます。`for`ループが終わったら、`i`の値は`5`なので、`5`が５回表示されます。

思い通りに`0`,`1`,`2`,`3`,`4`を表示するには、いくつかの方法があります。

一つの方法は、別の関数を呼び出して、新しい変数を作ることです。

[code]

    function printFive() {
        for (var i = 0; i < 5; i++) {
            setTimeout(makePrintFunction(i), i * 1000);
        }
    }
    
    function makePrintFunction(value) {
        return function () {
            console.log(value);
        }
    }
    
    printFive();
    
[/code]

[jsfiddle](http://jsfiddle.net/necxmkjp/)

`makePrintValue`が呼び出される度に新しい`value`という変数が生成され、その変数を使う関数が返されます。だから、`i`の値が変わっても、その`value`の値は変わりません。

もひとつの方法は[`bind`](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)を使うことです。

[code]

    function printFive() {
        for (var i = 0; i < 5; i++) {
            setTimeout(printValue.bind(null, i), i * 1000);
        }
    }
    
    function printValue(value) {
        console.log(value);
    }
    
    printFive();
    
[/code]

[jsfiddle](http://jsfiddle.net/jju41kvr/)

`bind`とは、関数を実行せずに前もって引数を渡して新しい関数を作り出すメソッドです。その時点で渡した値がキープされるので、`i`の値が変わっても、すでに渡した値に影響はありません。

ちなみに、この場合は`console.log`に直接`bind()`を呼び出す方法もあります：

[code]

    function printFive() {
        for (var i = 0; i < 5; i++) {
            setTimeout(console.log.bind(null, i), i * 1000);
        }
    }
    
    printFive();
    
[/code]


CC BY-SA 3.0
2014-12-19T13:57:37.983
2153
2014-12-19T13:57:37.983
4788
2152
answer


See
[for文でclick時の動作を定義したい](https://ja.stackoverflow.com/questions/1534/for%E6%96%87%E3%81%A7click%E6%99%82%E3%81%AE%E5%8B%95%E4%BD%9C%E3%82%92%E5%AE%9A%E7%BE%A9%E3%81%97%E3%81%9F%E3%81%84/1616#1616)

これと同じ回答になります。  
for 文内で即時関数を用いて値を束縛する必要があります。 詳しくはこちらをご覧ください。

<http://mattn.kaoriya.net/software/lang/javascript/20110523124612.htm>


CC BY-SA 3.0
2014-12-19T14:00:32.250
2154
2014-12-19T14:00:32.250
2017-04-13T12:52:39.113
-1
440
2152
answer


その様な場合は with({i:i}) などとして i の値を束縛するのがお手軽です:

[code]

    function printFive() {
        for (var i = 0; i < 5; i++) with({i:i}) {
            setTimeout(function () {
                console.log(i);
            }, i * 1000);
        }
    }
    
[/code]

[jsfiddle](http://jsfiddle.net/5fouk35q/)

説明:

(他の回答にもある様に) クロージャは (宣言された時の値をキャプチャするのではなく、) 変数 i
の参照を保持します。従って、クロージャが生成された後に外側の i
の中身が書き換われば、クロージャからも書き換わった値が見えます。更にいうならば、もしあるクロージャが変数 i
を書き換えれば、別のクロージャからもその書き換わった値が見えます。

ここで `with({i:i})` とすると、ループごとに `{i:i}` というオブジェクト (その時点での var i の値をメンバ i
に持つオブジェクト) が生成されます。そして `with(...)` に指定することで、クロージャから見える `i`
が、このループごとに生成されたオブジェクトのメンバ `i` に置き換わります。

(でも、この類の回答をしている人が他にいませんね…… この方法は私はたまに使うのですが、もしかして問題点でもあるのですかね……)

# 初投稿なのだが、こんな感じで投稿できるかな?


CC BY-SA 3.0
2014-12-19T16:23:03.880
2164
2014-12-19T16:23:03.880
5300
2152
answer


ちょっと時間が経っていますが、js使うならやっぱり再帰はちゃんと使えた方がいいと思うので再帰を使ったサンプルコードを貼らせて貰います。  
これなら同時に動いているsetTimeoutはある時点では一つしかないので止めたりするのも楽です。

[code]

    <html>
    <script>
    (function printFive(loop, now){
      if(!now)now=0;
      if(loop<now) return true;
      setTimeout(function(){
        now++;
        console.log(now);
        this(loop, now);
      }.bind(printFive), 1000);
    })(5)
    </script>
    </html>
[/code]


CC BY-SA 3.0
2015-02-11T01:17:23.257
6287
2015-02-11T01:17:23.257
8136
2152
answer