なでしこさんでイベントハンドラからローカル変数の値を使う？

# なでしこさんにおけるローカル変数

[📖 なでしこ3 マニュアル - 文法/ローカル変数](https://nadesi.com/v3/doc/index.php?%E6%96%87%E6%B3%95%2F%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E5%A4%89%E6%95%B0&show)

[なでしこ](https://nadesi.com/) において、[関数](https://nadesi.com/v3/doc/index.php?%E6%96%87%E6%B3%95%2F%E9%96%A2%E6%95%B0&show)内で

```text
(変数名)とは変数。
```

と書くことで、ローカル変数を宣言できる。

```text:ローカル変数を使う関数と使わない関数の例
アレは「うさぎ」。

●テスト関数Aとは
  アレとは変数。
  アレは「猫」。
  「テスト関数A：{アレ}」を表示。
ここまで。

●テスト関数Bとは
  アレは「犬」。
  「テスト関数B：{アレ}」を表示。
ここまで。

「グローバル：{アレ}」を表示。
テスト関数A。
「グローバル：{アレ}」を表示。
テスト関数B。
「グローバル：{アレ}」を表示。
```

このプログラムを実行すると、以下の実行結果が得られる。

```
グローバル：うさぎ
テスト関数A：猫
グローバル：うさぎ
テスト関数B：犬
グローバル：犬
```

:::note warn
変数宣言をしなくても、関数内での変数の変更が関数外に反映されないことがあるようである。
(関数の定義より前に関数外でその変数を使っていない場合？)

```text:変数宣言をしていないのに関数内での変数の変更が関数外に反映されない例
●テスト関数Aとは
  アレとは変数。
  アレは「猫」。
  「テスト関数A：{アレ}」を表示。
ここまで。

●テスト関数Bとは
  アレは「犬」。
  「テスト関数B：{アレ}」を表示。
ここまで。

アレは「うさぎ」。
「グローバル：{アレ}」を表示。
テスト関数A。
「グローバル：{アレ}」を表示。
テスト関数B。
「グローバル：{アレ}」を表示。
```

これを実行すると、以下の実行結果が得られる。

```text
グローバル：うさぎ
テスト関数A：猫
グローバル：うさぎ
テスト関数B：犬
グローバル：うさぎ
```
:::

# JavaScript におけるローカル変数とイベントハンドラ

JavaScript では、関数の中で定義したイベントハンドラでは、その関数内で定義したローカル変数を参照できる。

```javascript
function createCounter() {
  let count = 0;
  const countValue = document.createElement("span");
  countValue.textContent = "0";
  const countUp = document.createElement("button");
  countUp.setAttribute("type", "button");
  countUp.textContent = "++";
  countUp.addEventListener("click", () => {
    count++;
    countValue.textContent = count;
  });
  document.body.appendChild(countValue);
  document.body.appendChild(countUp);
  document.body.appendChild(document.createElement("br"));
}

for (let i = 0; i < 3; i++) {
  createCounter();
}
```

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="MYWYWgd" data-pen-title="local variable test" data-user="mike_cat" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/mike_cat/pen/MYWYWgd">
  local variable test</a> by MikeCAT (<a href="https://codepen.io/mike_cat">@mike_cat</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

このプログラムを実行すると、画面に3個のカウンタが現れ、それぞれ独立にカウントアップできる。
ローカル変数で「カウント値」と「カウント値を表示する要素」を管理することで、これを実現している。

# なでしこさんにおけるローカル変数とイベントハンドラ

なでしこで同様にコードを書いてみる。

```text:失敗するコード (ローカル変数)
●カウンタ作成とは
  変数のカウントは0。
  定数のカウント値は「0」のラベル作成。
  定数のカウントボタンは「++」のボタン作成。
  改行作成。
  カウントボタンをクリックした時には
    カウントを1増やす。
    カウント値にカウントをテキスト設定。
  ここまで。
ここまで。

3回、カウンタ作成。
```

<p class="codepen" data-height="430" data-default-tab="result" data-slug-hash="bNGNGBm" data-pen-title="nadesiko3 local variable example (bad)" data-user="mike_cat" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/mike_cat/pen/bNGNGBm">
  nadesiko3 local variable example (bad)</a> by MikeCAT (<a href="https://codepen.io/mike_cat">@mike_cat</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

このプログラムは動かない。
開発者ツールのコンソールを見ると、

> Uncaught Error: 『DOMテキスト設定』でクエリ『undefined』でDOM取得に失敗しました。

というエラーが出ている。
なでしこでは、JavaScript とは違い、関数内で定義したイベントハンドラからその関数のローカル変数を参照することはできないようである。

# ローカル変数にアクセスできないなら、グローバル変数を使えば……？

単純にローカル変数のかわりにグローバル変数を使うのでは、もちろんうまくいかない。

```text:失敗するコード (グローバル変数)
カウントとは変数。
カウント値とは変数。
カウントボタンとは変数。

●カウンタ作成とは
  カウントは0。
  カウント値は「0」のラベル作成。
  カウントボタンは「++」のボタン作成。
  改行作成。
  カウントボタンをクリックした時には
    カウントを1増やす。
    カウント値にカウントをテキスト設定。
  ここまで。
ここまで。

3回、カウンタ作成。
```

<p class="codepen" data-height="430" data-default-tab="result" data-slug-hash="VYwYwyr" data-pen-title="nadesiko3 global variable counter sample (bad)" data-user="mike_cat" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/mike_cat/pen/VYwYwyr">
  nadesiko3 global variable counter sample (bad)</a> by MikeCAT (<a href="https://codepen.io/mike_cat">@mike_cat</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

それぞれのボタンで独立してカウントしたいのに、どのボタンを押しても一番下のカウントが増えてしまう。
グローバル変数は1個だけで、全ての関数呼び出しで共有しているため、最後に作成したカウンタの情報しか保持できないのである。

# 提案手法：疑似ローカル変数

関数が呼び出されたとき、そのローカル変数として用いる辞書を生成する。
この辞書はグローバルの配列で管理し、添字をIDとして用いる。
このIDをイベントハンドラを設定するDOM要素の[ポケットに設定](https://nadesi.com/v3/doc/index.php?plugin_browser%2F%E3%83%9D%E3%82%B1%E3%83%83%E3%83%88%E8%A8%AD%E5%AE%9A&show)することで、イベントハンドラ内からも同じ辞書にアクセスし、ローカル変数のかわりに用いることができる。
イベントハンドラ (今回は「[クリック時](https://nadesi.com/v3/doc/index.php?plugin_browser%2F%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF%E6%99%82&show)」では変数「対象」にイベントが発火したDOM要素が入るため、これを用いればどの要素のポケットから辞書のIDを取得すればよいかを知ることができる。

```text:疑似ローカル変数を用いたコード
定数の疑似ローカル変数リストは空配列。

●疑似ローカル変数ID発行とは
  定数のIDは疑似ローカル変数リストの要素数。
  疑似ローカル変数リストに空辞書を配列追加。
  IDを戻す。
ここまで。

●(IDの)疑似ローカル変数取得とは
  疑似ローカル変数リスト@IDを戻す。
ここまで。

●カウンタ作成とは
  定数のローカル変数IDは疑似ローカル変数ID発行。
  定数のローカル変数はそれの疑似ローカル変数取得。
  ローカル変数$カウントは0。
  ローカル変数$カウント値は「0」のラベル作成。
  ローカル変数$カウントボタンは「++」のボタン作成。
  ローカル変数$カウントボタンにローカル変数IDをポケット設定。
  改行作成。
  ローカル変数$カウントボタンをクリックした時には
    定数のローカル変数は対象のポケット取得して疑似ローカル変数取得。
    ローカル変数["カウント"]を1だけ増やす。
    ローカル変数$カウント値にローカル変数$カウントをテキスト設定。
  ここまで。
ここまで。

3回、カウンタ作成。
```

<p class="codepen" data-height="430" data-default-tab="result" data-slug-hash="mydydaG" data-pen-title="nadesiko3 pseudo local variable example" data-user="mike_cat" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/mike_cat/pen/mydydaG">
  nadesiko3 pseudo local variable example</a> by MikeCAT (<a href="https://codepen.io/mike_cat">@mike_cat</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://public.codepenassets.com/embed/index.js"></script>

期待通り、それぞれのカウンタで独立してカウントができている。

:::note info
```text
ローカル変数["カウント"]を1だけ増やす。
```
のかわりに
```text
ローカル変数$カウントを1だけ増やす。
```
や
```text
(ローカル変数$カウント)を1だけ増やす。
```
と書くと、

> [エラー][文法エラー]main.nako3(13行目): 関数『カウンタ作成』の定義で以下のエラーがありました。 [文法エラー]main.nako3(21行目): 『クリック時には...』で無名関数の定義で以下の間違いがあります。 [文法エラー]main.nako3(23行目): 『増』文で定数が見当たりません。『(変数名)を(値)だけ増』のように使います。

というエラーになってしまった。
:::

:::note warn
なでしこさんの「ポケット」は、データをJSONでエンコードしてDOM要素の属性として格納する機能である。
そのため、ローカル変数として用いる辞書をそのままポケットに格納するのでは、うまくいかない。
値だけであれば変更後ポケットに再格納すれば保存できると思われるが、今回のコードのようにDOM要素を格納することはできない。
:::

# まとめ

なでしこさん (v3.6.45) では、JavaScript とは異なり、関数内で定義したイベントハンドラからその関数のローカル変数を参照することはできない。
ローカル変数を参照したい場合は、ローカル変数のかわりにグローバルな配列で管理する辞書を用い、そのIDをポケットで受け渡しすることで実現できる。