JavaScriptでよくある落とし穴とその回避策

代入演算子の誤用

条件式の中で誤って代入演算子=を使用すると、意図しない動作を引き起こします。比較を行う際は、等価比較の==または厳密な等価比較の===を使うべきです。

次のコードでは、変数xが10に代入され、その結果として真(true)が返されます。これは条件判定ではなく代入操作であるため、バグの原因になります。

let x = 0;
if (x = 10) {
    console.log("実行されます"); // 注意:これは条件ではなく代入
}

正しくは以下のように比較演算子を使用します。

let x = 0;
if (x === 10) {
    console.log("ここは実行されません");
}

比較演算子における型の扱い

抽象的な比較==では型変換が行われるため、予期しない一致が発生することがあります。

let a = 5;
let b = "5";
console.log(a == b);  // true(文字列と数値が等しいと判定)
console.log(a === b); // false(型が異なるため)

特にswitch文では、各caseとの比較に厳密等価(===)が使用されるため、データ型の整合性が重要です。

let value = 10;
switch(value) {
    case "10":
        console.log("ここは実行されない");
        break;
    case 10:
        console.log("ここが実行される");
        break;
}

加算と文字列連結の混同

+演算子は数値の加算にも、文字列の連結にも使われます。片方が文字列の場合、他方も自動的に文字列に変換されます。

let result1 = 20 + 30;       // 50(数値)
let result2 = 20 + "30";      // "2030"(文字列)
let result3 = 20 + 30 + "5";  // "505"(左から評価)
let result4 = "5" + 20 + 30;  // "52030"

この振る舞いは、演算順序とデータ型によって結果が変わるため注意が必要です。

浮動小数点数の精度問題

JavaScriptはIEEE 754に従って64ビット浮動小数点数を使用しており、一部の小数値は正確に表現できません。

let a = 0.1;
let b = 0.2;
let sum = a + b;
console.log(sum);           // 0.30000000000000004
console.log(sum === 0.3);   // false

これを避けるには、整数化して計算後に戻す方法があります。

let fixedSum = (a * 10 + b * 10) / 10; // 0.3

文字列の改行記述に関する制限

文字列リテラル内で物理的な改行を直接使うと構文エラーになります。

// 誤り
// let str = "Hello
// World";

// 正しい方法:エスケープシーケンスを使用
let str = "Hello \
World";

あるいはテンプレートリテラル(バッククォート)を使うことで、自然な改行が可能になります。

let multiline = `Hello
World`;

不要なセミコロンによる制御フローの破綻

if文の直後に誤ってセミコロンを置くと、その後のブロックは常に実行されます。

if (false); {
    console.log("ここは必ず実行される");
}

これは、if文が空の処理になり、後続の{}がただのコードブロックとして扱われるためです。

return 文の自動セミコロン挿入(ASI)

JavaScriptは行末にセミコロンがない場合、文の終わりと判断して自動的に挿入することがあります。これにより、return後に値が続くように書いても、実際にはundefinedが返されることがあります。

function getValue() {
    return
        "Hello";
}
console.log(getValue()); // undefined

正しくはreturnと値を同じ行に記述する必要があります。

function getValue() {
    return "Hello";
}

配列に対する名前付きインデックスの使用

JavaScriptの配列は数値インデックス専用です。文字列キーを使用すると、内部的に通常のオブジェクトに変換され、配列としての機能が失われます。

let arr = [];
arr[0] = "Alice";
arr["name"] = "Bob";

console.log(arr.length); // 1("name"はカウントされない)
console.log(arr.name);   // "Bob"
console.log(arr["name"]); // "Bob"

名前付きプロパティが必要なら、純粋なオブジェクトを使用すべきです。

配列・オブジェクトの末尾カンマの取り扱い

末尾にカンマを置くことは構文的には有効ですが、古い環境では問題になる可能性があります。また、配列の長さに影響を与える場合もあります。

// 避けたほうがよい
let items = [1, 2, 3, ];

// 推奨
let items = [1, 2, 3];

オブジェクトでも同様です。

let config = {
    mode: "strict",
    debug: true
}; // カンマなし

null と undefined の違い

nullは「意図的に空である」ことを示す値であり、undefinedは「未定義」または「値がまだ割り当てられていない」状態を表します。存在しない変数に対してtypeofを使わずに比較するとエラーになります。

// 危険:myObjが未定義だとエラー
// if (myObj !== null && myObj !== undefined)

// 安全なチェック
if (typeof myObj !== "undefined" && myObj !== null) {
    // 処理
}

ブロックスコープの理解

varで宣言された変数は関数スコープを持ち、ブロック(例:forループ内)での宣言でも関数全体で有効になります。

for (var i = 0; i < 5; i++) {
    // ロジック
}
console.log(i); // 5(ループ終了後もアクセス可)

これを防ぐには、letまたはconstを使用してブロックスコープを明確にします。

for (let j = 0; j < 5; j++) {
    // ロジック
}
// console.log(j); // ReferenceError: j is not defined

タグ: javascript 型変換 演算子 スコープ 浮動小数点

7月30日 06:13 投稿