JavaScriptの真偽値について
こんにちは、フロントエンドエンジニアです。
今回の記事ではフロントエンドエンジニアらしく、Webプログラミングで最も使用頻度が高い、JavaScriptについて取り上げたいと思います。
またその中でも、プログラムの制御構造として重要な条件分岐における「条件」の真偽を決定づける、真偽値について改めてまとめていきます。
他のプログラミング言語と同様に、JavaScriptにおいても真偽値(Boolean型)の値は、
- true(真)
- false(偽)
の2つのみが存在します。
この値を利用し、以下のような条件分岐や繰り返しにおいてプログラムの制御を行いますね。
1 2 3 4 5 6 7 8 9 10 11 |
// if文 if(boolean 条件) { // 条件がtrueの場合に実行する処理 } else { // 条件がfalseの場合に実行する処理 } // for文 for (制御変数; boolean 条件; カウンター) { // 条件がtrueの場合に実行する処理 } |
実際のプログラムを記述するにあたっては、上記のような条件分岐などの構文において
1 2 3 |
if (true) { console.log('true') } |
のように、条件としてboolean値を直接使用することはないでしょう。
通常は以下のように比較演算子によって値を比較し、それらの値が同値であるか、非同値であるかを比較しますね。
1 2 3 4 5 |
if (person.name === 'Jack') { console.log('この人は私の子供です') } else { console.log('この人は私の子供ではありません!') } |
ちなみに、同値/非同値であるかを比較する演算子には、===
と==
、!==
と!=
がありますが、バグの混入を予防するためにも、厳密な同一性の演算子である===
及び!==
を使用するべきでしょう。
というのも==
と!=
は、暗黙的な型変換を比較時に実行してしまうため、以下のように文字列と数値が同一であると比較されてしまうからです。
1 |
console.log(3 == '3') // true |
厳密な同一性の演算子である===
と!==
を使用すれば、挙動的には、型の異なるオペランドが厳密に等しくなることはありえなくなるので、プログラムの挙動の予測可能性が上がります。
undefined
はundefined
、null
はnull
とだけ同一であると判断されます。
(厳密でない等価演算子である==
の場合、undefined == null // true
となります)
このように、値を比較する場合は、厳密等価演算子を用いることで、プログラムの予測可能性を高め、バグを予防することが重要であるといえるでしょう。
では、以下のような「文字列の100と、文字列の10に数値の0を連結した値」の比較の場合、consoleには何が出力されるでしょうか。
1 2 3 4 5 |
if ('100' === '10' + 0) { console.log("'100'は'10' + 0と同値です") } else { console.log("'100'は'10' + 0と非同値です") } |
結果としては、'100'は'10' + 0と同値です
がconsoleに出力されます。
これは、文字列('10')と数値(0)を加算演算子で評価した場合、文字列の連結が実行されるためです。
結果として、左辺のオペランドである'10' + 0
は'100'
として評価されるため、条件'100' === '10' + 0
は'100' === '100'
として評価され、文字列同士の比較として、厳密に同値であると最終的に評価されます。
このように、型が予測できない値同士の比較の場合、予期しない挙動が実行される場合があるので、プログラムを記述する際には注意しなければなりません。
また、非数であることを表すNaN(Not a Number)
というグローバルオブジェクトのプロパティが存在します。
MDNドキュメントによると、NaNを返す演算には以下の5種類があります。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/NaN
- 数値が解釈できない (例えば parseInt("blabla") または Number(undefined))
- 結果が実数にならない数学演算 (例えば Math.sqrt(-1))
- オペランドが NaN である (例えば 7 ** NaN)
- 不確定形 (例えば 0 * Infinity または undefined + undefined)
- 文字列が関わる加算以外の何らかの演算 (例えば "foo" / 3)
このように導出されるNaNですが、値がNaNであるかどうかのチェックに、value === NaN
は使用しても意味がありません。
NaN同士の比較は、常にfalse
と評価されるためです。
値がNaNであるかをチェックしたい場合は、NumberのメソッドであるNumber.isNaN(value)
を呼び出しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const value = 3 / 'foo' // NaN if (value === NaN) { console.log('trueと評価される') } else { console.log('falseと評価される') } // 'falseと評価される'が出力される if (Number.isNaN(value)) { console.log('trueと評価される') } else { console.log('falseと評価される') } // 'trueと評価される'が出力される |
ソフトウェアエンジニア、特にWebプログラミングをするエンジニアとしては、JavaScriptを使用する機会は多いでしょう。
また、プログラムを書いていく上で、制御構造として真偽性を用いた条件を記述することは避けて通れません。
本記事で記述したことは真偽性の評価の氷山の一角であり、型の混ざった比較(文字列と数値の大小比較や、配列と数値の比較など)は非常に複雑で、挙動を把握していないと思わぬバグに悩まされることでしょう。
(そうしたバグに悩まされないためにも、予測できない値を比較のオペランドとして使用すべきでないというのは言うまでもありませんね)
そうしたバグに時間を費やさないためにも、真偽性の判定に使用される値の特性、型が異なる値を比較した場合の挙動については、ある程度把握しておくべきだ、と感じます。
MDN(2022年3月、ページを刷新しましたね)にはこうした比較演算に関する記述も充実していますし、ECMAScriptの仕様などに目を通すのも良いかもしれませんね。
https://www.ecma-international.org/