【2019年6月版】jsでクロージャに渡す変数はとりあえずconstしておけッ！！

# 結論

クロージャの中で使いたい変数の宣言に注意しよう。

- 参照のみの変数は `const` 宣言マスト。でもあとは気にしなくてOK。
- 書き込みしたい変数は `let` 宣言して**腹をくくる**。
- `var` は**使わない**。

時々、忘れてしまうのでメモです。

# クロージャ内から使用される変数に刮目せよ

js ではクロージャをバンバン使いますが、クロージャ内で使用する変数には適切な宣言をしておかないとバグを埋め込む原因となります。
例えば以下のようなコードで、

```javascript
// DB や APIなどから非同期で処理〜
function getPromise(c) {
    return new Promise((accept)=>accept(c));
}

// 何かのマスターを元にループで処理したい
const array = [1,2,3,4,5];
for(a of array) {
    msg = `[${a}] <- イマココ`;
    getPromise(a).then(v => {
        console.log(`値: ${v}`);
        console.log(`処理中: ${msg}`);
    })
     msg = `[${a}] <- 終了！お疲れ様でした。`
    console.log(msg);
}
```
 `getPromise(a).then(v => {...}` で変数 `a` の値が変数 `v` に渡されてきますが、この `v => {...}` の中で参照されている変数 `msg` の宣言はどうなっているでしょうか？

`msg = '[${a}] <- イマココ'` で宣言無しにいきなり変数定義してしまっています。熟練の方は一瞬で**をいをいお**と気づいていただけるでしょうが、例えば僕は数千行に渡るデータコンバート用の単発のバッチを~~涙ながらに~~jsでガリガリ書いているわけです。

DB `A` の テーブル `B` からデータを読み込み、 DB `C` の テーブル `D` のマスターを参照しつつデータを変換して DB `E` の テーブル `F`  へデータを書き込んでいくわけです。~~いちいち変数宣言してられるかーーー！！~~

・・・とりあえず区切りのいいところで試しに動かして見るか、とテスト環境を対象に上記のバッチを流してみると・・・

```console
$ npm start
[1] <- 終了！お疲れ様でした。
[2] <- 終了！お疲れ様でした。
[3] <- 終了！お疲れ様でした。
[4] <- 終了！お疲れ様でした。
[5] <- 終了！お疲れ様でした。
値: 1
処理中: [5] <- 終了！お疲れ様でした。
値: 2
処理中: [5] <- 終了！お疲れ様でした。
値: 3
処理中: [5] <- 終了！お疲れ様でした。
値: 4
処理中: [5] <- 終了！お疲れ様でした。
値: 5
処理中: [5] <- 終了！お疲れ様でした。
```

`処理中: ` のメッセージで `イマココ` を確認したかったのに、`終了` メッセージが表示されています。ウホー！
最初の5行の出力はちょっと目障りなので出力をやめてみましょう。

```javascript
function getPromise(c) {
    return new Promise((accept)=>accept(c));
}

const array = [1,2,3,4,5];
for(a of array) {
    msg = `[${a}] <- イマココ`;
    getPromise(a).then(v => {
        console.log(`値: ${v}`);
        console.log(`処理中: ${msg}`);
    });
    msg = `[${a}] <- 終了！お疲れ様でした。`;
}
```

これを実行してみます。

```console
$ npm start
> node index.js

値: 1
処理中: [5] <- 終了！お疲れ様でした。
値: 2
処理中: [5] <- 終了！お疲れ様でした。
値: 3
処理中: [5] <- 終了！お疲れ様でした。
値: 4
処理中: [5] <- 終了！お疲れ様でした。
値: 5
処理中: [5] <- 終了！お疲れ様でした。
```

終了メッセージに書き換わっている・・・。

いやいや、`getPromise(a)` の後で ```msg = `[${a}] <- 終了！お疲れ様でした。`;``` とmsgの代入処理をしているのでこの問題点はすぐに気づけるはずです。~~俺としたことが・・・ちょっと疲れたのかな。。。~~

それではこれではどうでしょう？

```javascript
function getPromise(c) {
    return new Promise((accept)=>accept(c));
}

const array = [1,2,3,4,5];
for(a of array) {
    msg = `[${a}] <- イマココ`;
    getPromise(a).then(v => {
        console.log(`値: ${v}`);
        console.log(`処理中: ${msg}`);
    });
}
```
`getPromise(a)` の後で `msg` には何も入れていません。というか `msg` は宣言してからクロージャの中でしか使用していません。よし、万全。指差し確認、OK！

では、バッチを動かしてみましょう。

```console
$ npm start
> node index.js

値: 1
処理中: [5] <- イマココ
値: 2
処理中: [5] <- イマココ
値: 3
処理中: [5] <- イマココ
値: 4
処理中: [5] <- イマココ
値: 5
処理中: [5] <- イマココ
```
`イマココ` メッセージが**全部 `5` になっている**ーーー！！！
これはメッセージに出力しているだけなので、`msg` が変わってしまっていても別に*フーン*で済むと思うかもしれません。
では、この `msg` 変数が**DBの書き込み先**だったり、**APIのエンドポイント**だったと想定したらどうでしょう？
すべてのデータが5番の接続先へ流れていきます。APIやRDBMSなら**引数エラー**や最悪でも**スキーマの不一致**などでフェイタルエラーで止まってくれるかもしれませんが、MongoDBやKVSなどの**スキーマレス**のNoSQLやただのフォルダ、アップロードするだけなどの場合は**普通に処理が完了してしまう**でしょう。
もう、白目、ですね・・・。想像しただけで**胃が縮む**思いです。

どうしてこうなったのか・・・？ 理解するのには最初の冗長なメッセージを出していたバージョンのプログラムをもう一度、見るのがわかりやすいのです。

```javascript
function getPromise(c) {
    return new Promise((accept)=>accept(c));
}

for(a of array) {
    msg = `[${a}] <- イマココ`;
    getPromise(a).then(v => {
        console.log(`値: ${v}`);
        console.log(`処理中: ${msg}`);
    });
    msg = `[${a}] <- 終了！お疲れ様でした。`;
    console.log(msg);
}
```
そして実行結果は、

```console
$ npm start
[1] <- 終了！お疲れ様でした。
[2] <- 終了！お疲れ様でした。
[3] <- 終了！お疲れ様でした。
[4] <- 終了！お疲れ様でした。
[5] <- 終了！お疲れ様でした。
値: 1
処理中: [5] <- 終了！お疲れ様でした。
値: 2
処理中: [5] <- 終了！お疲れ様でした。
値: 3
処理中: [5] <- 終了！お疲れ様でした。
値: 4
処理中: [5] <- 終了！お疲れ様でした。
値: 5
処理中: [5] <- 終了！お疲れ様でした。
```
となります。

forループの最後で宣言した `console.log(msg);` の結果である、

```
[1] <- 終了！お疲れ様でした。
[2] <- 終了！お疲れ様でした。
[3] <- 終了！お疲れ様でした。
[4] <- 終了！お疲れ様でした。
[5] <- 終了！お疲れ様でした。
```
がすべて先に表示されています。

つまり、先に**for ループ が完了している** ということです。 `Promise` で処理を後回しにしたので、すべての for ループが完了してから、`Promise` の `(accept)=>accept(c)` が呼び出され、`v => {...}` が呼び出されます。

・・・**`for`　ループが完了しているのに、for ループ内で宣言した変数`msg` はどうなっているのか・・・？**

これが**クロージャがクロージャたる所以**なのですが、`v => {...}` が宣言された時点でのすべての変数が**レキシカル環境**として記憶されています。要はどこかで変数 `msg` の存在を覚えててくれるわけですが、この変数が**書き込み有りか無しか**で持ち方が変わってしまう、というのがキモなワケです。

今回は手抜きをして変数宣言をすっ飛ばした結果、デフォルトの宣言である書き込み可能な `var` として定義されてしまいました。
よってクロージャの中では書き込み用の `msg` という`みんなで読み書きできる1つの場所`として変数が`msg`が使用されています。その結果、forの最後のループで書き込まれた`5`がすべてのクロージャで参照された、ということになります。

これはクロージャの中から値を取り出したい場合のような`明確な書き込み用途`の変数の場合は**当然の結果**です。
というわけで、書き込み用途ではない場合には明確に**読み取り専用**変数である**`const`の宣言がマスト**なのです。

ここでは`msg`は変わって欲しくない変数のため、以下のように `const` 宣言をちゃんとします。

```javascript
function getPromise(c) {
    return new Promise((accept)=>accept(c));
}

const array = [1,2,3,4,5];
for(a of array) {
    const msg = `[${a}] <- イマココ`;
    getPromise(a).then(v => {
        console.log(`値: ${v}`);
        console.log(`処理中: ${msg}`);
    });
}
```

そして、実行してみると・・・

```
$ npm start

> node index.js

値: 1
処理中: [1] <- イマココ
値: 2
処理中: [2] <- イマココ
値: 3
処理中: [3] <- イマココ
値: 4
処理中: [4] <- イマココ
値: 5
処理中: [5] <- イマココ
```

`const` 宣言をして、**宣言した時点の値**をちゃんとキャプチャさせた結果、ちゃんと 1〜5 の`イマココ`メッセージが表示されました。やれやれだぜ。。。

## `var` はやばい。

さらに今回は変数の宣言をせずにデフォルトの `var` が使われてしまいましたが、`var`は関数スコープで`function`オブジェクトの`プロパティ`のように振る舞うので、副作用が大きいです。

```javascript
function majisuka() {
    var x = 'マジで？'
    let y = '嘘でしょ？'
    {
        var x = 'マジすか？'
        let y = '本当に？'
    }
    console.log(`x is [${x}]`);
    console.log(`y is [${y}]`);
}
majisuka();
```
こんな適当なプログラムでも、ちゃんと結果を予測できますか？結果は以下のようになります。

```
x is [マジすか？]
y is [嘘でしょ？]
```

`var`で宣言した`x`はブロック内で宣言した`x`で見事に上書きされています。**マジすか？！**
そんなわけでクロージャ内で書き込みたい変数にはちゃんと `let` 宣言をしましょう！！

## ちなみに java では

```java
String msg = "こんにちわ";   // final 宣言していない

new Thread(() -> {
    msg = "こんばんわ";     // -> これは"コンパイルエラー"
    System.out.println(msg);
});
```

などの、クロージャ外で宣言された変数へのアクセスは**コンパイルエラー**となります。`final`宣言が `Must` で読み取り専用の変数しか渡せません。

例えば上記のように本当にマルチスレッドで動かすためのクロージャでは、クロージャの外部へ値を書き出すことはそのまま`スレッド間通信`となるので、変数への書き込みのような**お手軽な読み書き**じゃダメなのですね。
このように場合に応じてロックのストラテジを各自でちゃんと考えて用意してね、というわけなのです。

js はシングルスレッドで動くかなり特殊な言語で、クロージャが**同時に実行されることはない**ので、一つの変数の読み書き、というレベルで済んでしまいます。お手軽なのはいいですが、副作用は大きいので気をつけてね！というお話でした〜。

## 参考ページ

[クロージャ - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures)

[var - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/var)