学ぶ › JavaScript
言語ごとの特徴 · 41
JavaScript の等価と真偽
== と ===、falsy な値、?? と ||、すべて double の数、undefined と null、そして TypeScript が防いでくれること。
この単元は JavaScript · TypeScript 向けです。
ほかの言語は比較のときにこっそり型を変えません。
Python の "5" == 5 はただの False(大小比較 "5" < 5 は TypeError)、Java・C# はコンパイルエラーなので自分で変換し、Craykon の == も型が違えば偽です。
JavaScript は比較や真偽の判定のときに型をこっそり変えます。
下の例は入力で読んだ文字列 "5" と "0" を使い、== と ===、|| と ??、小数の計算、undefined と null がどう違う答えを出すかを見せます。
const [a, b] = require("fs").readFileSync(0, "utf8").trim().split("\n"); console.log(a == 5, a === 5, a === "5");console.log(a + 1, Number(a) + 1);console.log(0 == "", null == undefined, null === undefined); const falsy = [0, "", NaN, null, undefined, false];console.log(falsy.filter(Boolean).length, Boolean("0"), Boolean([])); const count = Number(b);console.log(count || 10, count ?? 10); console.log(0.1 + 0.2, 0.1 + 0.2 === 0.3);console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON);console.log(Number.MAX_SAFE_INTEGER + 2);console.log(String(2n ** 64n)); const nan = Number("abc");console.log(nan === nan, Number.isNaN(nan)); const user = { name: "Kim" };console.log(user.age, typeof null, user.age ?? "none");import { readFileSync } from "node:fs"; const [a, b]: string[] = readFileSync(0, "utf8").trim().split("\n"); // a == 5는 컴파일 오류 TS2367(string과 number는 같을 수 없어요). any로 검사를 끄면 JS와 똑같이 돌아요.const raw: any = a;const zero: any = 0;console.log(raw == 5, raw === 5, a === "5");console.log(a + 1, Number(a) + 1);console.log(zero == "", null == undefined, null === undefined); const falsy: unknown[] = [0, "", NaN, null, undefined, false];console.log(falsy.filter(Boolean).length, Boolean("0"), Boolean([])); const count: number = Number(b);console.log(count || 10, count ?? 10); console.log(0.1 + 0.2, 0.1 + 0.2 === 0.3);console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON);console.log(Number.MAX_SAFE_INTEGER + 2);console.log(String(2n ** 64n)); const nan: number = Number("abc");console.log(nan === nan, Number.isNaN(nan)); // user.age는 타입에 없으면 컴파일 오류 TS2339라서 age?: number로 적어 둬요const user: { name: string; age?: number } = { name: "Kim" };console.log(user.age, typeof null, user.age ?? "none");5 0
true false true 51 6 true true false 0 true true 10 0 0.30000000000000004 false true 9007199254740992 18446744073709551616 false true undefined object none
ポイント
==は型が違うと片方を変えて比べます。
だから"5" == 5、0 == ""、null == undefinedはどれもtrueです。===は型まで同じでなければなりません。
変換の規則は複雑なので、覚えるより、いつも===と!==を使います。+は片方が文字列ならつなぎます。"5" + 1は"51"ですが、"5" - 1は4です。
入力はいつも文字列なので、先にNumber(...)で変えます。- 偽とみなされる値(falsy)は
false、0、-0、0n、""、null、undefined、NaNだけです。"0"や空の配列[]は真です(Python では[]は偽)。 - 「値がない」が2種類あります。
undefinedは一度も入れていないこと(ない属性、渡さなかった引数、returnのない関数の値)、nullはわざと入れた「空」です。
両方まとめて調べるならx == nullかx ?? 既定値を使います。 NaNは自分自身とも等しくないのでNaN === NaNはfalseです。Number.isNaN(x)で調べます。typeof nullが"object"なのは初期の JavaScript の間違いが残ったものです。
言語ごとの書き方
| JavaScript | 比較は ===・!==、既定値は ??、文字列は Number(...) で数に変えます。ここの落とし穴は実行中に何の警告もなく通り過ぎます |
|---|---|
| TypeScript | 一部をコンパイル時に止めます: a == 5(string と number)は TS2367、x === NaN は TS2845、型にない user.age は TS2339。strict では age?: number を使う前に undefined かどうかを確かめさせます。ただし型は実行前に消えるので実行中はそのまま JavaScript で、 any や as を使うと検査が切れます(例の raw) |
よくある間違い
既定値を || で与えた
const count = 0;
console.log(count || 10, count ?? 10);→ 10 0 — || は左が偽とみなされると右を返すので、正しい値の 0 が 10 に変わります
null・undefined のときだけ右を返す ?? を使います。
小数を === で比べた
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);→ 0.30000000000000004、false — 数はすべて64ビットの浮動小数点数(double)1種類なので誤差が出ます
Math.abs(x - y) < Number.EPSILON のように差で比べます。
とても大きい整数をそのまま計算した
console.log(Number.MAX_SAFE_INTEGER + 2);→ 9007199254740992 — 整数は 2^53 − 1(9007199254740991)までしか正確でないので、...993 になりません
2n 64n のように n を付けた BigInt** で計算します。1n + 1 のようにふつうの数と混ぜると TypeError です。
言語ごとの違い
比較は
比較は
===、既定値は ?? で書きましょう。== と || は、"5" == 5 が真になったり 0 が 10 に変わったりと、エラーなしで違う答えを出します。