型を変換する
このレッスンで分かること
Number(値)で数値へ、String(値)で文字列へ明示的に変換できますparseIntとparseFloatは数字で始まる文字列から数を取り出します+は片方が文字列だと連結になるため、意図しない結果を生みます
型を変換する とは
文字列と数値を相互に変換します。入力欄から届いた
"3"を数値に直して計算できるようになる回です。
数字に見えても文字列
前のレッスンの最後で見たとおり、"3" と 3 は別物です。入力欄から受け取った値は、見た目が数字でもすべて文字列として届きます。この状態で計算しようとすると、期待とは違う結果になります。
const yearsText = "3";
console.log(yearsText + 1); // "31" 連結されてしまう
console.log(yearsText - 1); // 2 なぜか引き算はできる足し算だけが連結になり、引き算は数値として計算されます。+ は文字列の連結にも使う記号なので、片方が文字列だと連結が優先されるためです。- には連結の意味が無いので、JavaScript が文字列を数値に変換してから計算します。
この「勝手に変換される」動きを 暗黙の型変換 と呼びます。便利に見えますが、結果が読みにくくなるため、自分で明示的に変換するほうが安全です。
判断の基準 暗黙の型変換に頼らず、変換したい場所で自分で変換する。これだけで型に起因するバグの大半は防げます。
数値へ変換する
文字列を数値にする方法は主に 3 つあります。
console.log(Number("3")); // 3
console.log(parseInt("3", 10)); // 3
console.log(parseFloat("3.5")); // 3.5Number() は値全体を数値として読もうとします。数値として読めない文字が 1 文字でも混ざっていると NaN になります。parseInt() と parseFloat() は先頭から数字として読める部分だけを取り出し、途中で読めなくなったらそこで止まります。
変換結果の早見表
実際の挙動を並べると、次の通りです。手を動かして確かめながら読んでください。
| 式 | 結果 | 補足 |
|---|---|---|
Number("3") | 3 | 素直に変換される |
Number("3.5") | 3.5 | 小数もそのまま |
Number(" 5 ") | 5 | 前後の空白は無視される |
Number("") | 0 | 空文字は 0 になる |
Number("3年") | NaN | 余計な文字があると失敗 |
Number(null) | 0 | null は 0 として扱われる |
Number(undefined) | NaN | undefined は数にできない |
Number(true) | 1 | true は 1、false は 0 |
parseInt("3年", 10) | 3 | 読める所まで取り出す |
parseInt("3.9", 10) | 3 | 小数点以下は切り捨て |
parseInt("年3", 10) | NaN | 先頭が数字でないと失敗 |
parseInt("", 10) | NaN | 空文字は数にできない |
parseFloat("3.5kg") | 3.5 | 小数として読める所まで |
特に注意したいのが Number("") が 0 になる点と、parseInt("") が NaN になる点の違いです。未入力の扱いを間違えると「入力していないのに 0 として計算された」という不具合につながります。
NaN は Not a Number の略で、数値型でありながら「数として意味を持たない」ことを表す特別な値です。typeof NaN は "number" になります。NaN は計算に混ざると結果もすべて NaN になるため、変換に失敗したことに気付かないまま画面に NaN と表示される事故がよく起きます。
現場の話
parseIntの第 2 引数10は 10 進数として読むという指定です。省略しても現代の環境ではほぼ 10 進数として扱われますが、意図を明示するために書く習慣を付けておくと安全です。
文字列へ変換する
数値を文字列にするには String() を使います。
console.log(String(3)); // "3"
console.log(String(3.5)); // "3.5"
console.log(String(true)); // "true"
console.log(String(null)); // "null"
console.log(String(undefined)); // "undefined"String() はどんな値でも文字列にできるため、Number() のように失敗することがありません。null や undefined もそれぞれ "null" "undefined" という文字列になります。
数値には toFixed() という便利なメソッドもあります。小数点以下の桁数をそろえた文字列を返します。
const views = 1234.567;
console.log(views.toFixed(1)); // "1234.6" 文字列になる点に注意toFixed() の戻り値は数値ではなく文字列です。そのまま計算に使うと、また連結が起きてしまいます。
変換の落とし穴
暗黙の型変換は、演算子によって振る舞いが変わります。代表的なものを整理すると、次の通りです。
| 式 | 結果 | 何が起きているか |
|---|---|---|
"3" + 1 | "31" | 数値が文字列に変換され連結 |
1 + "3" | "13" | 順番が違っても連結 |
"3" - 1 | 2 | 文字列が数値に変換され引き算 |
"3" * 2 | 6 | 掛け算も数値として計算 |
true + 1 | 2 | true が 1 に変換される |
"" + 1 | "1" | 空文字との連結 |
+ だけが特別扱いだと覚えておけば、ほとんどの混乱は避けられます。数値として扱いたいなら、+ を書く前に Number() を通しておきます。
const yearsText = "3";
const years = Number(yearsText);
console.log(years + 1); // 4 正しく計算できる覚え方 外から入ってきた値は、受け取った直後に変換して型をそろえる。この一手間を入口で済ませておくと、後の処理で型を気にせずに済みます。
変換する場所を決めておく
変換をどこで書くかは、コードの読みやすさを大きく左右します。おすすめは、値を受け取った直後にまとめて変換してしまう書き方です。関数の先頭で const years = Number(yearsText); と変換しておけば、以降の行では数値として安心して扱えます。
反対に、使う直前で毎回 Number() を書くと、同じ変換が何か所にも散らばります。1 か所でも書き忘れると、そこだけ文字列のまま計算されて連結が起きます。入口で 1 回だけ変換する、という方針を決めておいてください。
よくある間違い
1 つ目は、変換せずに足し算してしまうケースです。
const workCountText = "3";
const total = workCountText + 1;
console.log(total); // "31" 期待は 4Number(workCountText) + 1 と書けば 4 になります。
2 つ目は、Number("") が 0 になることを見落とすケースです。
const input = ""; // 未入力
console.log(Number(input) + 1); // 1 未入力なのに計算が通る未入力かどうかは、変換する前に元の文字列で確かめる必要があります。この判定の材料は次のレッスンで扱います。
3 つ目は、NaN になったことに気付かないケースです。
const years = Number("3年");
console.log(years); // NaN
console.log(years + 1); // NaN 画面に NaN と表示されるNaN は他の値と比べても等しくならないという特殊な性質があるため、判定には Number.isNaN(years) を使います。まずは「変換は失敗しうる」という前提を持つことが大切です。
要件
- 関数名は nextYearLabel、引数は yearsText (文字列または数値) の 1 つ
- Number で数値に変換してから 1 を足す
- 戻り値は
4 年目の形の文字列。数値を String で文字列にしてから連結する
入出力例
nextYearLabel("3") → "4 年目"
nextYearLabel(3) → "4 年目"
nextYearLabel(" 5 ") → "6 年目"
nextYearLabel("0") → "1 年目"
nextYearLabel("") → "1 年目"