JavaScript(や、Typescript)において、letはなるべく使いたくありません。
できることなら扱う値は全てconstで定義したいところですが、forループを扱うときなどは、letを使って然るべきではあります。
letそのものが悪いわけではないのです。
// 至って自然な処理。
for (let i = 0; i < 9; i++) {
console.log(i);
}
そもそも何故、なるべくletを使いたくないのか、という話ですが、letで定義した値は後から上書き可能だからですね。
let a = 1;
a = 2; // letは上書き可能。何の問題ない処理。
const a = 1;
a = 2; // constは上書き不可。エラー。
では上書き可能だと何が問題なのか
という話に発展しますが、上書き可能だと、その値に対する信頼が一切なくなってしまうからです。
let a = 1;
// 色々複雑な処理。
console.log(a); // このaの中に入っているのが何なのか分からない。
ここで「色々複雑な処理」を経たあと、aという値をどうしても使いたくなった場合、aの中身に入っている値が本当に使ってもいい値であるのか否かを確認するために、「色々複雑な処理」を読み解く必要があります。
そして、「色々複雑な処理」は、本当に複雑な処理なので、最終的にaの中に入っているaに対して100%の確信を持つことができません。
「十中八九この時のaは○○だと思うけど、もしかすると△△である可能性も否定しきれない。......わかんないけど」
みたいなことになります。
constで定義すれば、こんな事態にはなり得ません。
const a = 1;
// 色々複雑な処理。
console.log(a); // このaの中に入っているのは、絶対に、1
断言できます。100%の確信をもって言えます。aの中に入っているのは「1」です、と。
よくあるletの使用パターン
forの処理以外でletを使う例です。
function judge(flag) {
let result;
if (flag) {
result = "真"
} else {
result = "偽"
}
console.log(result); // resultを使った処理
}
よくあるケースかと思われます。
こういった時に、letを使わないで同じ処理を実装する小技を紹介します。
function handleFlag(flag) {
let result;
if (flag) {
result = "真"
} else {
result = "偽"
}
return result;
}
function judge(flag) {
const result = handleFlag(flag);
console.log(result); // resultを使った処理
}
letを使わざるを得ない部分だけを、handleFlag()という一つの関数に隔離すれば、メインの処理ではletを使わずに済みます。
しかしこの例だと、「こんなことのために、他では使わないfunctionを定義するのは面倒」という声が聞こえてきそうです。
であれば、こうしましょう。
function judge(flag) {
const handle = () => {
let result;
if (flag) {
result = "真"
} else {
result = "偽"
}
return result;
}
const result = handle();
console.log(result); // resultを使った処理
}
このようにメインの処理の中だけで完結する関数を一時的に定義してしまえば、無駄に外部にfunctionを増やすようなことはありません。
しかしこの例でも、「わざわざこの処理のために、定義する値を増やしたくない」という声が出てくるかもしれません。
であれば、こうします。
function judge(flag) {
const result = (() => {
let result;
if (flag) {
result = "真"
} else {
result = "偽"
}
return result;
})();
console.log(result); // resultを使った処理
}
即時実行関数 という手法です。
こうすれば、わざわざ一度だけしか使わない処理のために、定義する値を増やすこともなく、これから使用する値をconstで定義して使用することができます。
今回はif文で例を出しましたが、switch等でも同じようなことができます。割と便利です。
という小技の紹介でした。
余談1
個人の開発であれば、上記で紹介したようなテクニカルな手法をどれだけ使っても問題無いですが、共同開発の場合だと一考の余地があります。
何故かというと、分かりにくいからです。
無名関数がそもそもわかりづらいのに、即時実行関数なんてものだと尚更です。
そもそも今回、letではなくconstベースで値を定義しようとしたのは、信頼のある処理を実装したいからでした。
共同開発だと、チームには色んな人がいます。当然、初学者もいるでしょう。
こういうわかりづらい処理を実装したせいで、チームのメンバーがコードを理解できず、信頼できず、読み解くために余計な時間をかけてしまうような事態になってしまっては、本末転倒です。
こういうテクニカルな処理がどうしても必要、という場合であれば仕方ないですが、読み解きやすい処理でも似たような実装ができるのであれば、それに越したことはありません。
今回の例であれば、以下のような例が無名関数も使わず、ちょうどいい気もしますね。
function judge(flag) {
let tmpResult;
if (flag) {
tmpResult = "真"
} else {
tmpResult = "偽"
}
const result = tmpResult; // ここでconstに入れ直す。以降、tmpResultは使わない。
console.log(result); // resultを使った処理
}
余談2
今回は、constを使えば絶対安心! と受け取られかない内容の記事を書きましたが、別にconstで定義したからと言って、値は絶対不変、とはなりません。
オブジェクトを定義した場合、その中身は可変になります。
const arr = [1, 2];
arr.push(3);
console.log(arr); // [1, 2, 3]
const obj = {
a: 1,
b: 2,
}
obj.a = 3;
console.log(obj); // {a: 3, b: 2}
余談3
今回の記事の内容をより厳密に話すと、スコープの話になります。
本記事のタイトルには「letをなるべく使わないようにする」とありますが、実際に挙げた小技のコード例だと普通にletが出てきています。
では一体何が変わったのかと言うと、「メインの処理でletを使わなくて済むようになった」という点が変わりました。
本編ではスコープというワードを使いたくなかったので、「メインの処理」と色々ぼかしていますが、本編で紹介した小技をより厳密に表現すると、
「閉じられたスコープの中でletを定義したお陰で、letで定義した可変の値が、外側から参照される心配がなくなって安心できる小技」
となります。
例えば本編で紹介した以下のコード例
function judge(flag) {
const result = (() => {
let result;
if (flag) {
result = "真"
} else {
result = "偽"
}
return result;
})();
console.log(result); // resultを使った処理
}
こちらを見て、resultを連続で定義していることに違和感を持たれた方もいるかもしれません。
const result = (() => {
let result;
この部分ですね。
constとletのそれぞれでresultを定義しています。
これが可能なのは、スコープが違うからです。
const result = 1;
let result = 1; // エラー
これはエラーになります。
スコープが同じだからです。
let result = 1;
const result = 1; // エラー
これもスコープが同じなので、エラーになります。
// ↓ 外側のスコープで定義しているresult
const result = (() => {
let result; // ← 内側のスコープで定義しているresult
....
})();
console.log(result);
これはスコープが違うのでエラーにはなりません。
要するに {}の外か内か、という話です。
そして内側のスコープで定義した値は、外側から直接参照することができません。(逆に外側の値は内側のスコープから参照可能です。)
よって、外側のメイン処理でletを使わなくて済むようになったので安心、という内容でした。
正直スコープの話を上手く説明できた気がしないので(すみません)、気になる方はもっと深く調べてみるか、以下のコード例とにらめっこしてみてください。
function judge(flag) {
// 外側のスコープ
const hoge = "外hoge";
const foo = "外foo";
const result = (() => {
// 内側のスコープ。
let result;
const hoge = "内hoge";
result = hoge;
console.log(foo); // 外foo
console.log(hoge) // 内hoge
return result;
})();
console.log(hoge); // 外hoge
console.log(result); // 内hoge
}
実際のコンソールには以下の順で出力されます。
外foo
内hoge
外hoge
内hoge