JavaScriptでPHPの静的変数（static変数）みたいな事をやる方法


## 導入

[h:導入]: #%E5%B0%8E%E5%85%A5

PHPには、[静的変数（static変数）](http://php.net/manual/ja/language.variables.scope.php#language.variables.scope.static)という、便利な構文が存在します。

```php:PHPで静的変数（static変数）を使ったコードの例
<?php
function test(){
	// 変数"$a"を初期化
	static $a = 0;

	// 変数"$a"をカウントアップ
	$a++;

	// 変数"$a"の値を返す
	return $a;
}

// ループでtest関数を10回実行する
for($i = 0; $i < 10; $i++){
	echo 'out: ' . test();
	echo '<br>';
}
```

```text:出力結果
out: 1
out: 2
out: 3
out: 4
out: 5
out: 6
out: 7
out: 8
out: 9
out: 10
```

このように、二回目以降の関数呼び出しで、直前に実行した変数の値を再利用することができます。この構文を応用すれば、生成に時間がかかる値をキャッシュしたりすることができます。

さて、JavaScriptには、この便利な静的変数（static変数）が存在しません。しかし、似たような事を行う方法が存在します。
[関数のプロパティとして宣言する方法][h:関数のプロパティ]と、[クロージャを駆使する方法][h:クロージャ]の２つです[^方法は２つある]。

[^方法は２つある]: これ以外の方法もあるかもしれませんが、著者は、この２つの方法しか知りません。

## 関数のプロパティ

[h:関数のプロパティ]: #%E9%96%A2%E6%95%B0%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3

関数のプロパティとして静的変数（static変数）を宣言する方法は、このようなやり方になります。
後述しますが、**重大な[デメリット][h:関数のプロパティ/デメリット]が存在する**ため、このやり方よりも**[クロージャを駆使したやり方][h:クロージャ]を強くオススメ**します。

```js:関数のプロパティとして静的変数（static変数）を宣言する例
function test() {
    // プロパティ"a"を初期化
    if (!('a' in test)) {
        // もし、プロパティ"a"が未定義であれば、
        // 初期値の0を代入する
        test.a = 0;
    }

    // プロパティ"a"をカウントアップ
    test.a++;

    // プロパティ"a"の値を返す
    return test.a;
}

// ループでtest関数を10回実行する
for (var i = 0; i < 10; i++) {
    console.log(
        'out: ' + test()
    );
}
```

```text:出力結果
out: 1
out: 2
out: 3
out: 4
out: 5
out: 6
out: 7
out: 8
out: 9
out: 10
```

### 原理

[h:関数のプロパティ/原理]: #%E5%8E%9F%E7%90%86

JavaScriptの関数は、オブジェクト(連想配列、辞書、ハッシュみたいなモノ)の側面も持っています。よって、関数`test`のプロパティ`a`を動的に追加し、そこに値を格納することで静的変数（static変数）を再現できます。

例では、`test.a`（関数`test`のプロパティ`a`）が、PHPにおける静的変数（static変数）`$a`の役割を果たしています。最初に`test.a`が定義されているか確認し、未定義の場合に初期値の`0`を代入して初期化しています。

```js:プロパティ"a"が定義済みかを確認し、未定義であれば0を代入して初期化する部分
// プロパティ"a"が定義済みか確認
if (!('a' in test)) {
    // 定義されていなければ、初期値の0を代入して初期化
    test.a = 0;
}
```

以降、この`test.a`を静的変数（static変数）`$a`と同じように扱い、同じ処理を実現させています。

### メリット

[h:関数のプロパティ/メリット]: #%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88

* 後述の[クロージャを利用した方法][h:クロージャ]に比べると、PHPの静的変数（static変数）に近い書き方ができる

### デメリット

[h:関数のプロパティ/デメリット]: #%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88

* `test.a`のように、書き方が長くなる
* 初期化の構文が割とややこしい
* [横着して変数に代入すると上手く動かない][h:関数のプロパティ/デメリット/横着して変数に代入すると上手く動かない]
* [外部から書き換えられる危険がある][h:関数のプロパティ/デメリット/外部から書き換えられる危険がある]
* [名前が組み込みのプロパティやメソッドと被るとヤバイ][h:関数のプロパティ/デメリット/組み込みのプロパティやメソッドと被るとヤバイ]

特に、最後の２つはけっこう重大な問題になります。

#### 横着して変数に代入すると上手く動かない

[h:関数のプロパティ/デメリット/横着して変数に代入すると上手く動かない]: #%E6%A8%AA%E7%9D%80%E3%81%97%E3%81%A6%E5%A4%89%E6%95%B0%E3%81%AB%E4%BB%A3%E5%85%A5%E3%81%99%E3%82%8B%E3%81%A8%E4%B8%8A%E6%89%8B%E3%81%8F%E5%8B%95%E3%81%8B%E3%81%AA%E3%81%84

`test.a`を短くしたくなっても、横着して以下のように変数`a`に代入してはいけません。上手く動作しなくなります。

```js:変数aに格納したい時の間違ったやり方
function test() {
    // プロパティ"a"を初期化
    if (!('a' in test)) {
        // もし、プロパティ"a"が未定義であれば、
        // 初期値の0を代入する
        test.a = 0;
    }

    // プロパティ"a"を変数"a"に代入
    var a = test.a;

    // 変数"a"をカウントアップ
    a++;

    // 変数"a"の値を返す
    return a;
}

// ループでtest関数を10回実行する
for (var i = 0; i < 10; i++) {
    console.log(
        'out: ' + test()
    );
}
```

```text:出力結果
out: 1
out: 1
out: 1
out: 1
out: 1
out: 1
out: 1
out: 1
out: 1
out: 1
```

`test.a`を変数`a`に代入した時点で、この2つは別の変数として扱われてしまいます。変数`a`に1を加算しても、それが`test.a`に反映されず、結果、`test.a`の値は初期値の`0`のまま変わらず、いつまでも`1`が出力されてしまうのです[^1が出力される]。

[^1が出力される]: 初期値は`0`ですが、出力前に必ずカウントアップされるため、返り値は`1`になります。

もし、`test.a`を変数`a`に代入して利用したい場合、変更を加えた変数`a`を最後に`test.a`に代入すれば解決します。

```js:変数aに格納したい時の正しいやり方
function test() {
    // プロパティ"a"を初期化
    if (!('a' in test)) {
        // もし、プロパティ"a"が未定義であれば、
        // 初期値の0を代入する
        test.a = 0;
    }

    // プロパティ"a"を変数"a"に代入
    var a = test.a;

    // 変数"a"をカウントアップ
    a++;

    // returnの直前で変数"a"をプロパティ"a"に代入
    test.a = a;

    // 変数"a"の値を返す
    return a;
}

// ループでtest関数を10回実行する
for (var i = 0; i < 10; i++) {
    console.log(
        'out: ' + test()
    );
}
```

```text:出力結果
out: 1
out: 2
out: 3
out: 4
out: 5
out: 6
out: 7
out: 8
out: 9
out: 10
```

#### 外部から書き換えられる危険がある

[h:関数のプロパティ/デメリット/外部から書き換えられる危険がある]: #%E5%A4%96%E9%83%A8%E3%81%8B%E3%82%89%E6%9B%B8%E3%81%8D%E6%8F%9B%E3%81%88%E3%82%89%E3%82%8C%E3%82%8B%E5%8D%B1%E9%99%BA%E3%81%8C%E3%81%82%E3%82%8B

プロパティという特性上、その値は、関数の外からでも容易に読み書きできてしまいます。

```js:外側からプロパティを書き換えられ、影響が出てしまう例
function test() {
    // プロパティ"a"を初期化
    if (!('a' in test)) {
        // もし、プロパティ"a"が未定義であれば、
        // 初期値の0を代入する
        test.a = 0;
    }

    // プロパティ"a"をカウントアップ
    test.a++;

    // プロパティ"a"の値を返す
    return test.a;
}

// ループでtest関数を10回実行する
for (var i = 0; i < 10; i++) {
    console.log(
        'out: ' + test()
    );
}

// プロパティ"a"の値を読み出す
console.log('aの値は、');
console.log(test.a);

// プロパティ"a"の値を書き換えてみる
console.log('aを-10に上書き');
test.a = -10;

// 再び、ループでtest関数を10回実行する
for (var i = 0; i < 10; i++) {
    console.log(
        'out: ' + test()
    );
}
```

```text:出力結果
out: 1
out: 2
out: 3
out: 4
out: 5
out: 6
out: 7
out: 8
out: 9
out: 10
aの値は、
10
aを-10に上書き
out: -9
out: -8
out: -7
out: -6
out: -5
out: -4
out: -3
out: -2
out: -1
out: 0
```

この例では、単に迷惑なだけですが、使い方によってはエラーを吐いて動きが止められてしまう危険性もあります。

#### 組み込みのプロパティやメソッドと被るとヤバイ

[h:関数のプロパティ/デメリット/組み込みのプロパティやメソッドと被るとヤバイ]: #%E7%B5%84%E3%81%BF%E8%BE%BC%E3%81%BF%E3%81%AE%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%84%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%A8%E8%A2%AB%E3%82%8B%E3%81%A8%E3%83%A4%E3%83%90%E3%82%A4

JavaScriptの関数はオブジェクトです。そして、JavaScript関数には、最初から存在する[プロパティ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/prototype#Properties)や[メソッド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/prototype#Methods)が存在しています。
この「最初から存在するプロパティやメソッド」は、JavaScriptを使いこなす上で有用な機能を提供してくれます。特に`apply`メソッドや`bind`メソッドは、けっこう高頻度で使用します。

しかし、このプロパティやメソッドと被る名前のプロパティを静的変数（static変数）として使うコードを書いてしまうと、いろいろ問題が起きてしまいます。
例えば、`test.apply`を使った関数を書いてしまうと…

```js:静的変数（static変数）の名前をapplyにしてしまった例
function test() {
    // プロパティ"apply"を初期化
    if (!('apply' in test)) {
        // もし、プロパティ"apply"が未定義であれば、
        // 初期値の0を代入する
        test.apply = 0;
    }

    // プロパティ"apply"をカウントアップ
    test.apply++;

    // プロパティ"apply"の値を返す
    return test.apply;
}

// ループでtest関数を10回実行する
for (var i = 0; i < 10; i++) {
    console.log(
        'out: ' + test()
    );
}
```

```text:出力結果
out: NaN
out: NaN
out: NaN
out: NaN
out: NaN
out: NaN
out: NaN
out: NaN
out: NaN
out: NaN
```

このように、おかしな事が起きてしまいます。
またそれだけでなく、この例の関数`test`の`apply`メソッドは、もはや使い物にならなくなってしまうのです。

## クロージャ

[h:クロージャ]: #%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3

[クロージャ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures)を駆使する方法は、このようなやり方になります。

```js
// 処理内容の関数を生成
var test = function() {
    // 変数"a"を初期化
    var a = 0;

    // 処理内容の（外側でtest関数になる）無名関数を返す
    return function() {
        // 変数"a"をカウントアップ
        a++;

        // 変数"a"の値を返す
        return a;
    };
}();

// ループでtest関数を10回実行する
for (var i = 0; i < 10; i++) {
    console.log(
        'out: ' + test()
    );
}
```

```text:出力結果
out: 1
out: 2
out: 3
out: 4
out: 5
out: 6
out: 7
out: 8
out: 9
out: 10
```

### 原理

[h:クロージャ/原理]: #%E5%8E%9F%E7%90%86-1

[なぜクロージャ（Closure）と言うのか？ - Qiita](https://qiita.com/mochizukikotaro/items/7403835a0dbb00ea71ae)

クロージャについては、この記事が分かりやすいので、コッチを読んでください。この記事を読んだという前提で解説します。

このコードのtest関数を定義する箇所では、無名関数と即時関数を駆使しています。なのでまず、関数に名前をつけて分解してみます。

```js:test関数の定義部分を分かりやすく分解
// クロージャを定義するための関数"f_def_static"（仮の名前）を定義
var f_def_static = function() {
    // 静的変数（static変数）"a"を初期化
    var a = 0;

    // 処理内容の関数"f_main_process"（仮の名前）を定義
    var f_main_process = function() {
        // 変数"a"をカウントアップ
        a++;

        // 変数"a"の値を返す
        return a;
    };

    // 処理内容の関数"f_main_process"そのものを返す
    return f_main_process;
};

// 関数"f_def_static"を実行し、関数"f_main_process"に"test"という名前をつけてtest関数にする
var test = f_def_static();
```

まず、`f_main_process`関数が、本来の`test`関数に相当する処理を実行する関数です。
変数`a`の宣言を含んでいない点以外は、[PHPの`test`関数][h:導入]と変わりません。

次に、`f_def_static`関数は、以下の処理を行います。

* 静的変数（static変数）`a`の初期化
* `f_main_process`関数の定義
* `f_main_process`関数を返り値として返す

初期化された変数`a`は、`f_main_process`関数の内部から再利用できます。
そして、`f_main_process`関数を返り値として`f_def_static`関数の外に出し、`test`変数に格納して、`test`関数として実行した時ですら、変数`a`は再使用できてしまいます。
この仕組みを応用し、静的変数（static変数）を再現します。

#### 関数名の省略

[h:クロージャ/原理/関数名の省略]: #%E9%96%A2%E6%95%B0%E5%90%8D%E3%81%AE%E7%9C%81%E7%95%A5

ただし、いちいち関数に名前をつけても面倒です。なので、`f_def_static`関数の名前を取り、即時関数にしてしまいます。

0. `f_def_static`関数を括弧で囲む

    ```js
    // クロージャを定義するための関数"f_def_static"（仮の名前）を定義
    // 値なので、丸括弧で囲っても問題なし
    var f_def_static = (function() {
        // 変数"a"を初期化
        var a = 0;

        // 処理内容の関数"f_main_process"（仮の名前）を定義
        var f_main_process = function() {
            // 変数"a"をカウントアップ
            a++;

            // 変数"a"の値を返す
            return a;
        };

        // 処理内容の関数"f_main_process"そのものを返す
        return f_main_process;
    });

    // 関数"f_def_static"を実行し、関数"f_main_process"に"test"という名前をつけてtest関数にする
    var test = f_def_static();
    ```

0. `f_def_static`関数の定義内容を、実行している場所に持ってくる

    ```js
    // クロージャを定義するための関数を定義＆実行
    // 関数"f_main_process"に"test"という名前をつけてtest関数にする
    var test = (function() {
        // 変数"a"を初期化
        var a = 0;

        // 処理内容の関数"f_main_process"（仮の名前）を定義
        var f_main_process = function() {
            // 変数"a"をカウントアップ
            a++;

            // 変数"a"の値を返す
            return a;
        };

        // 処理内容の関数"f_main_process"そのものを返す
        return f_main_process;
    })();
    ```

0. `f_def_static`関数を囲っていた括弧は要らなくなったので、取る

    ```js
    // クロージャを定義するための関数を定義＆実行
    // 関数"f_main_process"に"test"という名前をつけてtest関数にする
    var test = function() {
        // 変数"a"を初期化
        var a = 0;

        // 処理内容の関数"f_main_process"（仮の名前）を定義
        var f_main_process = function() {
            // 変数"a"をカウントアップ
            a++;

            // 変数"a"の値を返す
            return a;
        };

        // 処理内容の関数"f_main_process"そのものを返す
        return f_main_process;
    }();
    ```

また、`f_main_process`関数も、定義内容を`return`文の前に持ってくることができます。

0. `f_main_process`関数の定義内容を、`return`文で返している場所に持っていく。

    ```js
    // クロージャを定義するための関数を定義＆実行
    var test = function() {
        // 変数"a"を初期化
        var a = 0;

        // 処理内容の関数を定義＆返す
        return function() {
            // 変数"a"をカウントアップ
            a++;

            // 変数"a"の値を返す
            return a;
        };
    }();
    ```

このように、関数名を省略し、コンパクトにすることで、こんな感じになるわけです。

```js:コンパクトになったtest関数の宣言
// 処理内容の関数を生成
var test = function() {
    /*
     * 静的変数（static変数）の定義
     */

    // 変数"a"を初期化
    var a = 0;

    // 処理内容の（外側でtest関数になる）無名関数を返す
    return function() {
        /*
         * 静的変数（static変数）を使用した処理内容
         */

        // 変数"a"をカウントアップ
        a++;

        // 変数"a"の値を返す
        return a;
    };
}();
```

### メリット

[h:クロージャ/メリット]: #%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88-1

* PHPの静的変数（static変数）と同じように、単なる変数として利用できる
* 外部から書き換えられない
* 単なる変数なので、プロパティやメソッドと被る心配はしなくても良い

### デメリット

[h:クロージャ/デメリット]: #%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88-1

* [全体的に構文がややこしい][h:クロージャ/デメリット/全体的に構文がややこしい]
* [静的変数（static変数）のためだけに、余計な関数が要る][h:クロージャ/デメリット/静的変数（static変数）のためだけに、余計な関数が要る]
* [うっかり`var`宣言を忘れてしまうと、ヤバイ][h:クロージャ/デメリット/うっかりvar宣言を忘れてしまうと、ヤバイ]

#### 全体的に構文がややこしい

[h:クロージャ/デメリット/全体的に構文がややこしい]: #%E5%85%A8%E4%BD%93%E7%9A%84%E3%81%AB%E6%A7%8B%E6%96%87%E3%81%8C%E3%82%84%E3%82%84%E3%81%93%E3%81%97%E3%81%84

クロージャを駆使しているため、はじめて読むと「？？？」となってしまう可能性があります。
[即時関数や無名関数を駆使してコンパクトにしてしまう][h:クロージャ/原理/関数名の省略]と、さらによく分からない構文になってしまいます。

また、関数の処理内容を内側に書かなくてはならないため、書き換えも若干面倒くさくなります。

#### 静的変数（static変数）のためだけに、余計な関数が要る

[h:クロージャ/デメリット/静的変数（static変数）のためだけに、余計な関数が要る]: #%E9%9D%99%E7%9A%84%E5%A4%89%E6%95%B0static%E5%A4%89%E6%95%B0%E3%81%AE%E3%81%9F%E3%82%81%E3%81%A0%E3%81%91%E3%81%AB%E4%BD%99%E8%A8%88%E3%81%AA%E9%96%A2%E6%95%B0%E3%81%8C%E8%A6%81%E3%82%8B

静的変数（static変数）を使うという、ただそれだけのために、余計な関数（[名前をつけて分解したコード][h:クロージャ/原理]の`f_def_static`関数に相当する関数）を１つ定義しなくてはなりません。
加えて、その余計な関数は、一回実行してしまえば二度と使いません。
このため、関数を生成する負荷により、[パフォーマンスが劣化する恐れがあります](https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures#Performance_considerations)。

もっとも、昨今の端末やブラウザは高性能になっているため、普通のWebページやWebアプリでそれほど気にする必要はありません。
ただし、例えばゲーム（特に、高負荷な処理を短い間にこなさなくてはならないVRゲームなど）を作ろうとした時などは、この僅かな劣化がFPSに影響してしまうかもしれません。

#### うっかり`var`宣言を忘れてしまうと、ヤバイ

[h:クロージャ/デメリット/うっかりvar宣言を忘れてしまうと、ヤバイ]: #%E3%81%86%E3%81%A3%E3%81%8B%E3%82%8Avar%E5%AE%A3%E8%A8%80%E3%82%92%E5%BF%98%E3%82%8C%E3%81%A6%E3%81%97%E3%81%BE%E3%81%86%E3%81%A8%E3%83%A4%E3%83%90%E3%82%A4

静的変数（static変数）の定義部分で使用している`var`宣言をうっかり忘れてしまうと、グローバル変数になってしまいます。
グローバル変数はどこからでも書き換えられるため、[外部から書き換えられる危険][h:関数のプロパティ/デメリット/外部から書き換えられる危険がある]が生じてしまいます。

```js:うっかりvar宣言を忘れたために、グローバル変数aの書き換えで影響が出てしまう例
// 処理内容の関数を生成
var test = function() {
    // 変数"a"を初期化
    // …あ、var宣言忘れてる
    a = 0;

    // 処理内容の（外側でtest関数になる）無名関数を返す
    return function() {
        // 変数"a"をカウントアップ
        a++;

        // 変数"a"の値を返す
        return a;
    };
}();

// ループでtest関数を10回実行する
for (var i = 0; i < 10; i++) {
    console.log(
        'out: ' + test()
    );
}

// グローバル変数"a"の値を読み出す
console.log('aの値は、');
console.log(a);

// グローバル変数"a"の値を書き換えてみる
console.log('aを-10に上書き');
a = -10;

// もう一度、ループでtest関数を10回実行する
for (var i = 0; i < 10; i++) {
    console.log(
        'out: ' + test()
    );
}
```

```text:出力結果
out: 1
out: 2
out: 3
out: 4
out: 5
out: 6
out: 7
out: 8
out: 9
out: 10
aの値は、
10
aを-10に上書き
out: -9
out: -8
out: -7
out: -6
out: -5
out: -4
out: -3
out: -2
out: -1
out: 0
```