【初心者】JavaScript のカウンターの書き方。色々な書き方から無限の学びを得る。

【初心者】JavaScript のカウンターの書き方。色々な書き方から無限の学びを得る。
【初心者】JavaScript のカウンターの書き方。色々な書き方から無限の学びを得る。

Work illustrations by Storyset

こんにちは、開発チームでエンジニアをしている和田です。
最近は担当しているプロダクトの開発メンバーが入れ替わったりとバタバタしている毎日です🏃‍♂️

いろんな開発メンバーがいると十人十色のコードが生まれます。また、自分自身に焦点を当ててみても過去の自分と今の自分では書き方に差があるはず... なんて哲学的なことを考えていると、一つの処理を様々な書き方で書いてみたくなりました(どうゆうことやねん笑)

というわけで今回は JavaScript 初心者の方向けにカウンターのいろんな書き方を簡単に紹介させていただきます。

JavaScript でカウンターをいろんな書き方で書いてみる

変数を使ったカウンターの書き方

まずは最も簡単な変数を使った書き方です。

let counter = 0;

for (let i = 0; i < 10; i++) {
  console.log(counter++);
}

console.log(`counter: ${counter}`);
console.log(`counter: ${counter}`);
0
1
2
3
4
5
6
7
8
9
counter: 10
counter: 10

プログラミングを最初に学んだときのループ処理などでよく見る例ですね。
ループの中では最初に counter 変数に入っている値をコンソールに表示した後にインクリメントを行います。 当然、ループを抜けた後の counter 変数の中身は常に 10 となります。

関数のプロパティを使ったカウンターの書き方

プロパティと聞くとオブジェクトをイメージされる方が多いかもしれません。
JavaScript では関数は特殊なオブジェクトとして定義されているので、関数の宣言を行っていればオブジェクトのように . でプロパティの値を操作することができます。

const uniqueInteger = () => uniqueInteger.counter++;
uniqueInteger.counter = 0;

for (let i = 0; i < 10; i++) {
  console.log(uniqueInteger());
}

console.log(`counter: ${uniqueInteger.counter}`);
console.log(`counter: ${uniqueInteger.counter}`);
0
1
2
3
4
5
6
7
8
9
counter: 10
counter: 10

uniqueInteger 関数の返り値に uniqueInteger.counter++ とすることで、uniqueInteger 関数の counter プロパティの値を返してからインクリメントを行います。
関数を宣言した時点では counter プロパティには何も値が入っていないので、宣言の前後で uniqueInteger.counter = 0; という初期化が必要となります。 ループの中のみで関数を実行しているので、ループの外では現在格納されている値を呼び出すことができます。
カウンターの値を初期化するのが少し手間ではありますが、「どの関数を何回実行したか?」が強調された書き方です。

即時実行関数とクロージャを使った書き方 その1

即時実行関数やクロージャと聞いて馴染みのない方もいるかも知れません。
まずはサンプルコードを確認しましょう。

const uniqueInteger = (() => {
  let counter = 0;
  return () => counter++;
})();

for (let i = 0; i < 10; i++) {
  console.log(uniqueInteger());
}

console.log(`counter: ${uniqueInteger()}`);
console.log(`counter: ${uniqueInteger()}`);
0
1
2
3
4
5
6
7
8
9
counter: 10
counter: 11

uniqueInteger は即時実行関数、すなわち宣言した直後に実行される関数なのでuniqueInter にはcounter++ という処理をもつ関数が格納されています。
したがってuniqueInteger() として実行すると counter をインクリメントした結果を返してくれるわけです。 ポイントとなるのはuniqueInteger 関数の counter 変数の値を直接、確認することができない点です。
今回の書き方は、これまでとは異なり、ループ後の値を保存しておきたい場合は専用の変数を設けるなどする必要があります。

【補足】即時実行関数で書かないとどのような結果になるか?

これまでのサンプルに対する理解を深めるためにあえて uniqueInteger を即時実行関数ではなく、ただの関数として実行してみます。

const uniqueInteger = () => {
  let counter = 0;
  return () => counter++;
};

for (let i = 0; i < 10; i++) {
  console.log(uniqueInteger()());
}

console.log(`counter: ${uniqueInteger()()}`);
console.log(`counter: ${uniqueInteger()()}`);
0
0
0
0
0
0
0
0
0
0
counter: 0
counter: 0

uniqueInteger 関数からは () => counter++ という関数が返されるので、実行時は uniqueInteger()() と少し変わった書き方をしてあげる必要があります。この場合、実行結果はループがあってもなくても 0 となります。これは関数が呼び出されるたびに counter 変数が counter = 0 として初期化されるためですね。一方で即時実行関数は宣言した後すぐに実行されるため、その後も uniqueInteger 関数内の counter 変数を初期化せずに参照し続けます。このような違いがあるため即時実行関数とクロージャを使うとカウンターとして機能するわけです。

即時実行関数とクロージャを使った書き方その2

関数のプロパティを使った書き方では、関数名とプロパティ名を . で繋ぐことで「どの関数を何回実行したか?」がわかりやすく書かれていました。 即時実行関数・クロージャでも同様の表現で記述することができます。

const uniqueInteger = (() => {
  let counter = 0;
  return {
    current: () => counter,
    counter: () => counter++,
    reset: () => (counter = 0),
  };
})();

for (let i = 0; i < 10; i++) {
  console.log(uniqueInteger.counter());
}

console.log(`counter: ${uniqueInteger.current()}`);
console.log(`counter: ${uniqueInteger.current()}`);
console.log(`counter: ${uniqueInteger.counter()}`);
console.log(`counter: ${uniqueInteger.counter()}`);
console.log(`counter: ${uniqueInteger.reset()}`);
console.log(`counter: ${uniqueInteger.counter()}`);
console.log(`counter: ${uniqueInteger.counter()}`);
0
1
2
3
4
5
6
7
8
9
counter: 10
counter: 10
counter: 10
counter: 11
counter: 0
counter: 0
counter: 1

先程の記述方法と異なっているのは uniqueInteger 関数の返り値がオブジェクト形式になっている点です。

さらにオブジェクトのそれぞれのプロパティの値は関数となっているため uniqueInteger.current() とすれば現在のカウンターの値を取得することができ、uniqueInteger.counter() でこれまで同様にインクリメントすることもできます。
また uniqueInteger.reset() とすることでカウンターの値をリセットすることもできます🙌

これまでのサンプルコードのいいところを全部詰め込んだコードですね。

どのカウンターがベストプラクティスか?

カウンターの処理1つとってもこれだけたくさんの書き方ができます。
では、どの書き方がベストプラクティスと言えるでしょうか?

答えは「時と場合による」です😇

とても簡単な処理では、最後の多彩な機能が盛り込まれたカウンターよりも最初の単純な変数によるカウンターの方が簡潔で理解もしやすいです。 チームや必要とされる機能などによって、どのような書き方が最適なのか?というのは常に変わるものなのでいろんな書き方を覚えておくのが良いかもしれませんね。


まとめ

最後まで読んでくださりありがとうございました🙇‍♂️
ただのカウンターの処理をいろんな表現で書かかせていただきました。
ライブラリから提供されている関数やメソッドにも今回紹介した書き方で実行するようなものが見られます。カウンターに関する知識だけでなく、今回紹介した内容がコードを読み進めるときに役に立つことを願っています🙏