JavaScriptで関数に独自プロパティを追加する方法とその活用例

![js-function-property-20231105.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1612505/cf84a5ee-7952-247d-99ee-391c811cec78.jpeg)

## 概要

JavaScript の値はすべて、論理型・数値型・文字列型など何らかの型を持ちます[^1]。
もちろん関数も型を持ち、JavaScript の関数はオブジェクト型とされています。

関数がオブジェクト型であるということは、関数がプロパティをもつことができるということです。今回の記事は JavaScript の関数に独自プロパティを定義する方法について解説させていただきます。

**この記事を読んでわかること**

- JavaScript の関数に独自プロパティを定義する方法
- 関数に独自プロパティを定義することでできること

**対象読者**

- JavaScript 初心者
- JavaScript にある程度馴染みのある開発者

**前提知識・環境**

- JavaScript の文法の基本的な知識

---

## JavaScript の関数に独自プロパティを定義する方法

### JavaScript のオブジェクトのプロパティ

本題に入る前に、JavaScript のオブジェクトのプロパティについて簡単に確認しておきます。

```javascript:オブジェクトのプロパティ
const person = {
  name: 'Mike',
  age: 30,
  hobbies: ['movie', 'baseball', 'reading']
};

console.log(person.name);   // Mike
console.log(person['age']); // 30
```

オブジェクトのプロパティというと最初に思いつくのはこのようなケースですね。
`person` オブジェクトは `name`, `age`, `hobbies` など様々な型のプロパティを持ち、`person.name` または `person['age']` のような記述方法によってその値を取得することができます。

また、下記のようにあとからプロパティを追加することも可能です。

```javascript:オブジェクトのプロパティの追加
person.occupation = 'engineer';
person['nationality'] = 'japan';
console.log(person.occupation);  // engineer
console.log(person.nationality); // japan
```

では関数のプロパティはどのように表現されるのでしょうか？

### JavaScript の関数のプロパティ

関数も先程の例と同様に関数名に `.` をつけてプロパティの値を取得できます。

```javascript:関数のプロパティ
const uniqueInteger = () => uniqueInteger.counter++;
uniqueInteger.counter = 0;

console.log(uniqueInteger.counter); // 0
console.log(uniqueInteger());       // 0

console.log(uniqueInteger.counter); // 1
console.log(uniqueInteger());       // 1

console.log(uniqueInteger.counter); // 2
console.log(uniqueInteger());       // 2
```

`uniqueInteger` 関数に `counter` プロパティを設けることで `uniqueInteger.counter` で `counter` プロパティの値を取得できます。

また、`uniqueInteger` 関数自体は `counter` プロパティ自体をインクリメントして返すので実行するたびに `counter` プロパティの値が更新されます。

### 関数のプロパティの使用例

機能として関数にプロパティをもたせることができるということがわかりました。
ではどのように利用できるのでしょうか？

例えば下記のような階乗の計算を行う `factorial` 関数について見てみましょう。

```javascript:階乗計算の関数
const factorial = (n) => {
  if (Number.isInteger(n) && n > 0) {
    if (!(n in factorial)) {
      factorial[n] = n * factorial(n - 1);
    }
    return factorial[n];
  } else {
    return NaN;
  }
};

factorial[1] = 1;
console.log(factorial(6)); // 720
console.log(factorial[5]); // 120
```

`factorial` 関数は引数に任意の整数を受け取り、$n$ の階乗を計算します。
$n$ 番目のプロパティが保存されていなかった場合は `factorial[n]` に計算結果を保存します。

したがって、`factorial(6)` を実行済みの状況であれば `factorial[5]`（5の階乗の計算結果）や `factorial[3]`（3の階乗の計算結果）はプロパティとして保存された値からすぐに呼び出す事ができます。

このように JavaScript の関数のプロパティの使用例として実行結果をキャッシュとして保存しておくことが挙げられます。

---

## JavaScript の関数のプロパティに関する個人的意見

今回紹介した中で `uniqueInteger` 関数を使って実行回数のカウントを行いました。
`uniqueInteger` 関数でプロパティを使用するメリット・デメリットそれぞれ考えて行きたいと思います。

### `uniqueInteger.counter` で関数の実行回数を管理するメリット

`uniqueInteger.counter` で実行回数を管理するメリットは、**`counter` が `uniqueInteger` 関数の実行回数である** とはっきりわかることだと思います。

例えば下記のように `uniqueInteger` 関数のスコープ外で変数宣言を行う場合、`counter` は何に対して回数を数えているのか示されていません。

```javascript:counterを関数のスコープ外で宣言する場合
let counter = 0;
const uniqueInteger = () => counter++;

console.log(uniqueInteger()); // 0
console.log(uniqueInteger()); // 1
```

`uniqueInteger` 関数の処理を確認することで、その実行回数を数えていることを察することができますが、誤って `counter` 変数の値を関係のない箇所で更新してしまうかもしれません。

`uniqueInteger.counter` として管理することで、どの関数の実行回数であるかがわかりやすくなります。

### `uniqueInteger.counter` で関数の実行回数を管理するデメリット

サンプルコードで示したように `uniqueInteger.counter` として関数の実行回数を管理する場合、プロパティの初期値を関数のスコープ外で定める必要があります。

```javascript:プロパティの初期値の設定
const uniqueInteger = () => uniqueInteger.counter++;
uniqueInteger.counter = 0; // 初期値の宣言
```

もし、初期値の宣言を忘れたり、コードを管理する過程でこの部分に余計な変更が加えられると予期せぬバグの原因となりそうです。

メリット・デメリットの両方について考えたうえで、今回のケースではクロージャを使った記述方法が筆者の好みです。クロージャを使った方法についてはまた別の記事で解説させていただきます。

---

最後まで読んでくださりありがとうございました🙇‍♂️
JavaScript のプロパティと考えると最初に示したオブジェクトのプロパティと想像される方が多いと思います。
関数でもプロパティを使えることを知っておくだけでコードを読むときの手助けになるはずです。

[^1]: [mdn web docs JavaScript のデータ型とデータ構造](https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures)