【JavaScript】ループ内の配列にデータを追加【注意】

プログラミング

どうも、えんつかです!!
PHPばかり触ってきた身からするとJavaScriptでは想定と違う挙動が起きて、たまに驚きます。

今回の記事は、その中の一つだと思います。完全に「なぜ!?」となり、詰まってしまう原因となったので、記事として残して誰かの解決の糸口になれば嬉しいです。

【JavaScript】ループ内の配列にデータを追加【注意】

早速ですが、皆さんは以下のコードを見て出力はどうなると思いますか??


let hoge = ['a', 'b', 'c'];
let data = {};
let list = [];
    for (let i = 0; i < hoge.length; i++) {
        let func = (function(){
            data.title = hoge[i];
            return data;
        })( );
        list.push(func);
    }
console.log(list);  //----出力----

もし次のように考えていた方は、大正解です!


[
    0: {title: 'c'},
    1: {title: 'c'},
    2: {title: 'c'},
]

もし、正解された方は流石です!もう以下の記事は読まなくて良いです笑!
もし、仮に次のように考えていた方は、私と同じです笑!
謎現象を一つずつ紐解いていきましょう!


[
    0: {title: 'a'},
    1: {title: 'b'},
    2: {title: 'c'},
]

なぜ、こうならんのや!
hoge~って感じです(頭を抱える)。
本当は、上の様な構成にしたかったのですが、残念でした。しょんぼり。

結論

以下の様にすればOKでした。


let hoge = ['a', 'b', 'c'];
let list = [];
    for (let i = 0; i < hoge.length; i++) {
        let func = (function(){
            let data = {};    // ここを変更 
            data.title = hoge[i];
            return data;
        })( );
        list.push(func);
    }
console.log(list);

変数宣言位置が正しくありませんでした。


let func = (function(){
    let data = {};
    data.title = hoge[i];
    return data;
})( );
list.push(func);

この部分のfunc 変数は即時実行関数と呼ばれ、グローバルスコープの汚染を避ける為に生まれました。

即時実行関数内の変数は、即時実行関数内でのみ参照可能です。

従来のECMAScript5までは、変数宣言にvarしか存在せず、varはどこで宣言しても参照してしまう、「巻き上げ」が発生してしまうので、即時関数を使うことで、参照を限定していました。

この部分で、そもそもfunc変数に即時関数を用いずに、pushすれば良いのではないかと思いますが、そちらも試してて結果的に全部同じ配列となります泣。
原因は、同じく変数宣言の位置です。
ですので、変数宣言の位置がちゃんとfor文内であれば、即時関数がなくても配列に格納されますのでお試しあれ!

for文内にクロージャーを用いたい場合は、即時実行関数に代替することで、ループ内で毎回処理が走ってくれます。

なんでこんなことが起きるのか?

結論としては、「for文内は一つのブロックスコープとして処理されるから」という考えに至りました。

ここは理解度80%位なのですが、デバッグしまくった結果、おそらく以下の様なことが処理として実行されている様です。

  • data変数宣言がfor文内だと、data変数にiの初期化条件が流れ、毎度dataにデータ格納され、その結果がfor文外のスコープに代入される。(狙い通り)
  • data変数宣言がfor文外だと、data変数に挿入するdata.titleはfor文外のブロックスコープなので、参照はできるものの、最終的に代入するのは、for文が全部処理された後にしかできない。(つまり、配列の最後がまとめて代入されるっぽいです。この部分がキーなんですけどちょっとモヤってます)

よくわかっていないのが、for文外のlist変数宣言直下で、listをデバッグするとすでにデータが格納されているっぽいんですよね…

for文の方が先に処理されるとかあるのかな…

ちょっとモヤッとする終わり方でしたが、おそらく上記の様な形で処理するので、for文を利用する時は気をつけましょう!

もちろん、上記以外の方法で処理させる手法もあると思います!
ので、参考の一つにしてください!

以上!

ほんじゃ〜ね〜