JavaScriptのエラーを直して覚える
実際に出力されるメッセージを読みながら、空欄を埋めて直します。全102問。間違えた問題は翌日・3日後・7日後に自動で再出題されます。
エラー別に調べる
初級
- undefinedのプロパティ読み取りエラーの原因undefinedになった値のプロパティやメソッドにアクセスしようとしたときに出る、JavaScript学習者が最も多く遭遇するエラーです。値が本当にundefinedになる典型的な場面を整理します。
- ReferenceError: x is not defined の原因と直し方存在しない変数・関数名を参照したときに出るエラーです。タイプミスだけでなく、let/constのブロックスコープの外から参照した場合にも同じエラーになります。
- 括弧の閉じ忘れで出る SyntaxError の原因と直し方丸括弧・波括弧・角括弧の閉じ忘れで出る構文エラーです。Node.jsはファイルの終わりまで読んでから対応が崩れていることに気づくため、エラー行が原因の行と一致しないことがあります。
- 配列の範囲外アクセスがエラーにならずundefinedになるJavaScriptの配列は範囲外の添字にアクセスしてもエラーを投げません。静かにundefinedを返すため、Java・Python・C#のように例外で気づくことができません。
- == が意図しない型変換で真になってしまう(===との違い)==は比較の前に両辺の型をそろえようとするため、文字列の"0"や空文字列、falseなどが数値の0と等しいと判定されてしまいます。===は型変換をしないため、この種の事故を避けられます。
- TypeError: x is not a function の原因と直し方関数ではない値を関数として呼び出そうとしたときに出るエラーです。変数名やメソッド名のタイプミス、あるいはプロパティと勘違いした呼び出しで頻発します。
- カンマの付け忘れで出る SyntaxError の原因と直し方配列・オブジェクトの要素や関数の引数を並べる際、区切りのカンマを付け忘れたときに出る構文エラーです。改行で区切っているつもりでも、JavaScriptはカンマが無いと1つの要素の続きとして読もうとします。
- const宣言で初期値を書き忘れた原因と直し方constで変数を宣言する際に初期値を書き忘れたときに出るエラーです。letやvarと違い、constは宣言と同時に値を代入することが文法上必須です。
- 同じ変数名を2回宣言したエラーの原因と直し方同じスコープ内で同じ名前の変数をlet・constで2回宣言したときに出るエラーです。コピー&ペーストで既存の変数名とかぶってしまうケースが典型です。
- "合計: " + a + b が期待した数値の合計にならない理由文字列と数値を+で連続してつなげると、最初の+で文字列に変換されてしまい、それ以降の+はすべて文字列連結として扱われます。数値同士を先に計算したい場合は括弧で明示する必要があります。
- 関数がreturn文を書き忘れてundefinedを返してしまう計算結果を変数に入れても、return文で明示的に返さない限り、関数はundefinedを返します。JavaScriptは他言語と違い、最後に評価した式を自動で返すことはありません。
- push()の戻り値は追加後の配列ではなく新しい要素数Array.prototype.pushは追加後の配列ではなく、追加後の要素数(数値)を返します。戻り値を配列として扱おうとすると、数値にはない配列のプロパティやメソッドでエラーや予期しない結果になります。
- 文字列同士の + が連結になる理由数字だけの文字列同士でも、+演算子は数値としての加算ではなく文字列としての連結を行います。数値として計算したい場合はNumber()などで明示的に変換する必要があります。
- length はメソッドでなくプロパティという原因配列や文字列の要素数はlengthというプロパティで取得します。他言語のsize()やcount()のような感覚でlength()と関数呼び出しの形で書くとエラーになります。
中級
- NaNを===やswitchで判定できない理由と直し方NaNはIEEE 754の仕様上、自分自身とも等しくないため、===はもちろん==でも決してtrueになりません。数値変換の失敗をチェックする際にこの性質を知らないと、判定が常にすり抜けます。
- メソッドを変数に代入・受け渡しするとthisが失われるオブジェクトのメソッドを変数に代入したり、別の関数に渡して呼び出したりすると、呼び出し時にレシーバーの情報が失われ、thisがオブジェクトを指さなくなります。
- ['1','2','3'].map(parseInt)が[1, NaN, NaN]になる理由Array.prototype.mapはコールバックに(要素, インデックス, 配列)の3引数を渡します。parseIntの第2引数は基数(何進数か)を表すため、インデックスがそのまま基数として渡されてしまいます。
- スプレッド構文{ ...obj }はネストした値までは複製しない(浅いコピー){ ...obj }や[...arr]は最上位のプロパティだけをコピーします。値がオブジェクトや配列の場合、コピー先とコピー元は同じ参照を共有したままです。
- Array.prototype.sort()が数値を文字列として並べ替える理由比較関数を渡さないsort()は、要素を文字列に変換してUnicodeコードポイント順に並べ替えます。数値の大小とは無関係な結果になります。
- 0.1 + 0.2 === 0.3 がfalseになる理由と直し方浮動小数点数は2進数で表現されるため、0.1や0.2のような10進の小数を正確に表せません。演算結果はごくわずかな誤差を含み、===での比較は失敗します。
- typeof null === 'object' の罠typeof演算子はnullに対して"object"を返します。これは言語仕様初期からのバグ扱いの挙動で、修正すると既存コードが壊れるため現在も残っています。オブジェクトかどうかの判定にtypeofだけを使うとnullをすり抜けさせてしまいます。
- forループのvarをletに変えないとコールバックが同じ値を指すvarはブロックスコープを持たないため、ループ内で生成した関数はすべて同じ変数を共有します。ループが終わったあとに関数を呼び出すと、全て最後の値を参照します。letはループの繰り返しごとに新しい束縛を作るため、この問題が起きません。
- 同じ内容のオブジェクト同士が === でfalseになる理由オブジェクトや配列の==・===は中身ではなく参照(メモリ上の同一性)を比較します。プロパティの値がすべて同じでも、別々に作られたオブジェクトは等しいと判定されません。
- 配列の分割代入で要素が足りないとundefinedになる(デフォルト値の付け方)配列の分割代入で、元の配列より多い変数を用意すると、対応する要素が無い変数はundefinedになります。各変数にデフォルト値を指定することでこれを防げます。
- JSON.parse()が投げるSyntaxErrorは実行時エラーであるJSON.parse()に不正な形式の文字列を渡すとSyntaxErrorを投げますが、これはJavaScript自体の構文解析エラーとは異なり、関数呼び出しの結果として実行時に発生する例外です。try/catchで捕まえられます。
- オプショナルチェイニング(?.)でnull/undefinedアクセスのエラーを防ぐネストしたプロパティの途中がnullやundefinedかもしれない場合、通常のドットアクセスではTypeErrorになります。?.を使うと、途中がnull/undefinedのときに例外を投げず、undefinedを返して処理を続けられます。
上級
- デフォルト引数はnullには適用されない(undefinedのときだけ)関数のデフォルト引数は、渡された値が厳密にundefinedのときだけ使われます。nullを渡すと「値が無い」という意図があっても、nullがそのまま使われてデフォルト値は適用されません。
- constへの再代入エラーの原因と直し方constで宣言した変数に再代入しようとしたときに出るエラーです。letやvarと違い、constは一度代入した値を別の値に差し替えることを許しません。
- sort()やreverse()は新しい配列を返さず元の配列を書き換えるArray.prototype.sortやreverseは呼び出した配列自体を並べ替える破壊的(ミューテーティング)メソッドです。戻り値だけを見て「新しい配列が作られた」と考えると、元のデータまで書き換わっていることに気づけません。
- Object.freeze()は1階層しか凍結しない(浅い凍結)Object.freeze()はオブジェクト自身の直下のプロパティの再代入・追加・削除だけを禁止します。プロパティの値がオブジェクトである場合、そのネストしたオブジェクトは凍結されず、自由に変更できてしまいます。
- プライベートフィールド外部アクセスの構文エラー原因#で始まるプライベートフィールドを、宣言したクラスの外からドットで直接読み書きしようとしたときに出るエラーです。他のエラーと違い、実行前の構文解析の段階で検出されます。
- インスタンス全体で共有したい値をstaticにし忘れるクラスのインスタンスをまたいで値を共有したい場合はstaticフィールドが必要です。staticを付け忘れると、その値はクラス自身ではなく各インスタンスに独立して属するため、期待した共有ができません。
- オブジェクトのキーは数値でも文字列に変換される(1と"1"は同じキー)JavaScriptのオブジェクトのプロパティキーは、Symbolを除きすべて文字列として扱われます。数値のキーを指定しても内部的には文字列に変換されるため、数値の1と文字列の"1"は同じキーとして衝突します。
- indexOf(NaN)が常に-1を返し、NaNを見つけられない理由Array.prototype.indexOfは===(厳密等価)で要素を探すため、自分自身とも等しくないNaNを絶対に見つけられません。includes()はSameValueZeroというNaNを特別扱いするアルゴリズムを使うため、NaNも正しく検出できます。