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つとってもこれだけたくさんの書き方ができます。
では、どの書き方がベストプラクティスと言えるでしょうか?
答えは「時と場合による」です😇
とても簡単な処理では、最後の多彩な機能が盛り込まれたカウンターよりも最初の単純な変数によるカウンターの方が簡潔で理解もしやすいです。 チームや必要とされる機能などによって、どのような書き方が最適なのか?というのは常に変わるものなのでいろんな書き方を覚えておくのが良いかもしれませんね。
まとめ
最後まで読んでくださりありがとうございました🙇♂️
ただのカウンターの処理をいろんな表現で書かかせていただきました。
ライブラリから提供されている関数やメソッドにも今回紹介した書き方で実行するようなものが見られます。カウンターに関する知識だけでなく、今回紹介した内容がコードを読み進めるときに役に立つことを願っています🙏