目次

目次

【JavaScript】varとletを辞めてconstを使っていこう

アバター画像
kitamura
アバター画像
kitamura
最終更新日2023/12/01 投稿日2023/12/01

この記事は レコチョク Advent Calendar 2023 の1日目の記事となります。

最近レコチョクに入社しました北村です。主にフロントエンド領域を中心に担当しております。 好きな音楽ジャンルはUKロックです。

varとletを辞めてconstを使っていこう

JavaScriptの安全性と生産性を上げるためのリファクタリングの第一歩として、

varは完全に捨て、letは極限まで避け、constを中心に実装してみましょう!

var,letを多用することの問題点から具体的な直し方まで、できるだけ初心者の方にも理解しやすいよう解説していきたいと思います。

varとletはなぜ安全性と生産性を下げるのか

varとletは、値をセットしなおす「再代入」が可能です。

let value = 123
value = 456

便利なように聞こえますが、 言い換えると、「 var,letで宣言された変数には、どんな値が入っているか分からない」ということです。

そうなると、以下のようにletで宣言された変数をしばらく後で使いたい場合、 中身がそれまでの処理の中で書き換えられている可能性があるので、 途中にある処理をすべて確認しなければなりません。

let value = 123

// たくさんの処理

console.log(value) // 123かもしれないし、書き変わっているかもしれない

途中の処理を読み解く時間自体が生産性の低下につながりますし、 人間が読む以上、 見誤ったり考慮漏れが発生してしまう可能性もあります。

そうなったとき、最後の行のconsole.logで意図しない値が出力されてしまうという不具合が発生します。

一方でconstは再代入ができません。

「再代入できない」と言うと不便なように聞こえますが、 「 再代入されていないことが保証されている」というふうに捉えましょう。

以下のvalueという変数は、その値が常に123であるということが言語レベルで保証されています。

const value = 123

// たくさんの処理

console.log(value) // 絶対に123

そのため、constで宣言された変数を利用するときに、 わざわざ途中のコードを読み解く必要がないですし、 読み間違いや考慮漏れも発生せず安全です。

  • var,letは、再代入による影響を目視で確認しなければいけないので、面倒かつ危険が伴う。
  • constは再代入がされていないことが保証されているので、安心して使える。

var,letの数が増えるほどコードの安全性と生産性は徐々に下がっていきますので、普段から constで書けるものはconstで書くようにしていきましょう。

letとvarは同じ?

letとvarは、再代入できるという点では同じですが、他にも違いがあります。

再宣言の可否と、変数を参照できる範囲が異なるのですが、 難しい言葉は抜きに、具体的には以下のような挙動の差を生みます。

例1:

const main = () => {
  var value = 1

  // ※

  console.log(value) // 1と出力させたい
}

上記の処理の、「※」の部分に以下のロジックを後付けで差し込むとします。

var value = 2
console.log(value) // 2と出力させたい

すると、実はこの「value」という変数名は既に使われていたわけですが、

varは再宣言可能であるという仕様のおかげでそのまま動作してしまうため、 既存の変数を置き換えてしまったことに気付かないまま、 最後の行で意図していなかった挙動が生じます。

const main = () => {
  var value = 1

  var value = 2
  console.log(value) // 2と出力させたい

  console.log(value) // 1と出力させたかったのに、いつの間にか2と出力されるようになってしまった
}

一方、もし上記のコードがletで書かれていた場合、 再宣言しようとした時点でエラーとなり、変数名が重複していることが発覚します。

const main = () => {
  let value = 1

  let value = 2 // Uncaught SyntaxError: Identifier 'value' has already been declared
  console.log(value) // ..

  console.log(value) // ..
}

例2:

letは、変数を参照できる範囲(スコープ)がブロック内外で区切られるため、 先ほどとは異なり、ifブロックなどの内側においては同名の変数を新たに宣言することが可能です。

let value = 1
if (true) {
  let value = 2
  console.log(value) // 2
}
console.log(value) // 1

そのため、上記のように、ブロックの内側だけでのみ使う変数を同名で宣言して使うことができ、 この2度目のvalueはブロック外にある最初のvalueを置き換えるものではないので、 最後の行にあるconsole.logの処理にも影響を与えません。

では、同じことをvarでやるとどうなるでしょうか?

varは変数を参照できる範囲(スコープ)の区切りがletとは異なり、 ifブロックなどの内外で同じ変数を参照してしまいます。 (varは関数のブロックが区切りとなります)

それに加えて先ほどの再宣言ができるという仕様と合わさると、また危険なことが起きます。

var value = 1
if (true) {
  var value = 2
  console.log(value) // 2
}
console.log(value) // 1のつもりが2に

さっきの例同様に、ifブロックの中だけで使うつもりでvalueを宣言して使ったつもりが、 ブロックの外にあった同名の変数を気づかないまま置き換えてしまいます。

結果として、 意図せずブロック外のその後の処理に影響を与えてしまいます。

  • varは、普段使いする中で意図しない変数の置き換えを生じさせてしまう恐れがある。
  • 再代入が必要な変数を宣言したい場合、varではなくletを使いましょう。

また、ここで説明した再宣言の可否とスコープの仕様は、letとconstで統一されているので、 この2つに絞って使えば仕様の違いによる混乱がなくなります。

letが必要なケース

極端な話をすると、letでなければいけないケースは限られており、 Webサイトでは主に「サイト利用中に変化する値を保持したいとき」です。

具体的には、

  • 非同期通信の前後
  • ユーザーによるクリック等のUI操作の前後
  • アニメーションやsetTimeout等の前後

で変数の中身を変えたいケースなどが該当します。

let clicked = false // クリックする前
const onClick = () => {
  clicked = true // クリックした後
}
let data = true // データ取得前
fetch('/api/xxx').then((response) => {
  data = response.data // データ取得完了後
})

こういうケース以外でletが用いられるケースは、 実装の仕方による場合がほとんどのため、極論そういったものは全てconstへの置き換えが可能です。

constへの置き換え具体例

前項で紹介した以外のケースでletを使ってしまっている場合、 実装方法の引き出しを増やしていくことでconstに置き換えていけると思います。

いくつかのパターンをご紹介します。

三項演算子を使う

let value = 123
if (isXxx()) {
  value = 456
}

const value = isXxx() ? 456 : 123

関数にする

let value = 123
if (isXxx()) {
  value = 456
} else if (isYyy()) {
  value = 789
}

const getValue = () => {
  if (isXxx()) return 456
  if (isYyy()) return 789
  return 123
}
const value = getValue()

このように関数化することは、他にもメリットがあります。

関数名が処理内容の説明の役割を担ってくれたり、 関数の責務が「valueを返すこと」だけになったことで、 安全なスコープに区切られたり、早期returnが使えたりと色々便利です。

Arrayのメソッドを使う

const values = [1, 2, 3, 4, 5]

// valuesに5が含まれているときだけtrueにしたい
let valuesIncludeFive = false
for (const value of values) {
  if (value === 5) {
    valuesIncludeFive = true
  }
}

const values = [1, 2, 3, 4, 5]

// valuesに5が含まれているときだけtrueにしたい
const valuesIncludeFive = values.some(value => value === 5)

このようなArrayをぐるぐる回しながらどうこうするケースは他にもたくさんあると思いますが、 filter, find, findIndex, map, someなどといったArrayメソッドの使い方を覚えていくことで綺麗に書けます。

参考: https://qiita.com/diescake/items/70d9b0cbd4e3d5cc6fce

おわりに①

この記事が、誰かのことを逆に困らせてしまうことのないように、 「letが必要なケース」に2点付け加えたいと思います。

  • constへ綺麗に書き換える方法が分からず困ってしまったとき
  • letで頑張って書き換えた結果、余計に難解になってしまったとき

実装時の思考の仕方が若干変わると思いますので、少しずつ慣れていきましょう。

おわりに②

近年のフロントエンドの流行りにおいては、TypeScriptを導入したり、関数型プログラミングの要素を取り入れたりと、コードの安全性を高めることが大切にされています。

本記事も、難しそうなワードをできるだけ避けて解説しましたが、内容自体は関数型プログラミングの考え方の一部を紹介したものです。

もしこのようなコードの安全性を高める考え方をさらに取り入れたい方は、「関数型プログラミング」について、できるだけ簡単そうな記事を見つけて読んでみていただければと思います。


明日の レコチョク Advent Calendar 2023 は2日目『【Kotlin】音楽アプリにAndroid Autoのアイテム表示を実装してみた』です。お楽しみに!

アバター画像

kitamura

目次