JavaScriptの基礎 - 比較演算子と論理演算子

提供: MochiuWiki : SUSE, EC, PCB

概要

JavaScriptには、値を比較するための複数の演算子が用意されている。
これらは大きく分けて、厳密な等価比較 (===、!==)、抽象的な等価比較 (==、!=)、大小比較 (<、>、<=、>=)、論理演算 (&&、||、!)、および最新の演算子 (??、?.、&&=、||=、??=) に分類される。

JavaScriptの比較演算子を正確に理解することは、バグのない堅牢なソースコードを記述するために不可欠である。

特に重要なのは、厳密等価演算子 (===)抽象等価演算子 (==) の違いである。

  • ===
    型変換を行わないため予測可能な動作をする。
  • ==
    型変換を行うため意図しない結果をもたらす可能性がある。


論理演算子 (&&||!) は単なる真偽値の演算にとどまらず、短絡評価によって値そのものを返す特性を持つ。
この特性を活用することにより、デフォルト値の設定や条件付き実行等を簡潔に記述できる。

ES2020で導入されたNull合体演算子 (??) と オプショナルチェーン (?.) は、nullundefined を安全に扱うための重要な演算子である。
また、ES2021で導入された論理代入演算子 (&&=||=??=) により、条件付き代入をより簡潔に記述できるようになった。


比較演算子

比較演算子は、2つの値を比較してその結果を true または false で返す演算子である。
JavaScriptでは、型変換を行うかどうかによって演算子の動作が大きく異なる。

厳密等価演算子 (===) と 厳密不等価演算子 (!==)

厳密等価演算子 (===) は、2つのオペランドが等しいかどうかを検査する演算子であり、型変換を一切行わない。
オペランドの型が異なる場合、常に異なるものと見なしてfalseを返す。
厳密不等価演算子 (!==) は、===の逆の結果を返す。

  • === の動作
    • 同じ型で同じ値の場合にtrueを返す。
    • 型が異なる場合は常にfalseを返す。
    • 型変換を行わないため、動作が予測可能である。
    • NaN === NaN は、falseを返す。(NaN は自分自身とも等しくない)

  • 使用すべき場面
    • ほぼ全ての等価比較において、===!== を使用すべきである。
    • 型の安全性が確保される。
    • コードの予測可能性が高まる。
    • パフォーマンス上の利点がある。(型変換が不要なため)


 // 厳密等価演算子 (===) の動作
 console.log(5 === 5);                // true
 console.log(5 === "5");              // false (型が異なる)
 console.log(true === 1);             // false (型が異なる)
 console.log(null === null);          // true
 console.log(undefined === undefined);// true
 console.log(NaN === NaN);            // false (NaN は自分自身と等しくない)
 
 // 厳密不等価演算子 (!==) の動作
 console.log(5 !== 5);               // false
 console.log(5 !== "5");             // true (型が異なる)
 console.log(null !== undefined);    // true (型が異なる)


抽象等価演算子 (==) と 抽象不等価演算子 (!=)

抽象等価演算子 (==) は、型が異なる場合に型変換を行ってから比較する演算子である。
この型変換の規則を Abstract Equality Comparison Algorithm と呼ぶ。
抽象不等価演算子 (!=) は、== の逆の結果を返す。

Abstract Equality Comparison Algorithm の主なルールを以下に示す。

  • 両方が同じ型の場合、=== と同じように比較する。
  • boolean 値が含まれる場合、trueは1、falseは0に変換する。
  • 数値と文字列を比較する場合、文字列を数値に変換する。
  • null と undefined を比較する場合、どちらもnullishとしてtrueを返す。
  • オブジェクトとプリミティブを比較する場合、オブジェクトをプリミティブに変換する。


代表的な型変換例を以下に示す。

 console.log(null == undefined);   // true (特別なルール)
 console.log(0 == false);          // true (false が 0 に変換)
 console.log(1 == true);           // true (true が 1 に変換)
 console.log("0" == false);        // true ("0" が 0 に、false が 0 に変換)
 console.log("" == false);         // true ("" が 0 に、false が 0 に変換)
 console.log("1" == 1);            // true ("1" が 1 に変換)
 console.log([1] == 1);            // true ([1] が "1" を経由して 1 に変換)
 console.log(null == 0);           // false (null は 0 に変換されない)
 console.log(undefined == 0);      // false (undefined は NaN に変換)


=== と == の違い

===== の最大の違いは、型変換を行うかどうかである。
=== は型変換を行わないため動作が予測可能であるが、== は複雑な型変換ルールを持つため意図しない結果をもたらすことがある。

下表に、比較結果の対照表を示す。

=== と == の比較結果
比較式 === の結果 == の結果 型変換の内容
5 と 5 true true なし
5 と "5" false true 文字列が数値に変換
true と 1 false true booleanが数値に変換
null と undefined false true 特別なルール (どちらもnullish)
"" と 0 false true 文字列が数値に変換
"0" と false false true 両方が数値に変換 (それぞれ0)
[] と 1 false false 配列[]が""を経由して0に変換
[1] と 1 false true 配列[1]が"1"を経由して1に変換
NaN と NaN false false NaN は自分自身と等しくない


=== が推奨される理由を以下に示す。

  • 予測可能な動作
    型変換の複雑さがなく、ソースコードの意図が明確になる。
  • バグの減少
    予期しない型変換による問題が発生しない。
  • パフォーマンス
    型変換のコストがないため、わずかに高速である。
  • 業界標準
    ESLint等のソースコード品質ツールが === の使用を推奨している。


大小比較演算子

大小比較演算子 (<、>、<=、>=) は、2つの値の大小関係を比較する演算子である。
型によって比較方法が異なるため、特に文字列と数値の混合比較に注意が必要である。

  • 数値比較
    両方のオペランドが数値の場合、数値の大きさを直接比較する。
     console.log(5 > 3);              // true
     console.log(10 <= 10);           // true
     console.log(2 < 8);              // true
     console.log(3.14 >= 3.14);       // true
    

  • 文字列比較 (辞書順)
    両方のオペランドが文字列の場合、Unicodeコードポイントを使って比較する。
    文字列の内容が数値であっても、文字列として辞書順で比較される点に注意が必要である。
     // 文字列比較は辞書順 (Unicode コードポイント) で行われる
     console.log("apple" < "banana");  // true
     console.log("10" > "2");          // false ("1" < "2" のため)
     console.log("a" < "b");           // true
     console.log("abc" < "abd");       // true (3文字目で "c" < "d")
    

  • 数値と文字列の混合比較
    数値と文字列を比較する場合、文字列が数値に変換されてから比較が行われる。
     console.log(5 > "3");            // true ("3" が 3 に変換)
     console.log("10" < 20);          // true ("10" が 10 に変換)
     console.log("hello" > 5);        // false ("hello" が NaN に変換)
    

  • 特殊な値の処理
    • true / falseは、1 / 0に変換される。
    • nullは、0に変換される。
    • undefinedはNaNに変換されるため、比較結果は常にfalseになる。

     console.log(true > false);       // true (1 > 0)
     console.log(null <= 0);          // true (null が 0 に変換)
     console.log(undefined < 5);      // false (undefined が NaN に変換)
     console.log(undefined > 5);      // false (NaN との比較は常に false)
    



論理演算子

JavaScriptの論理演算子 (&&、||、!) は、真偽値だけでなくあらゆる型の値を操作できる。
重要な特性は、短絡評価により値そのものを返すことである。

JavaScriptでは、以下に示すいずれかの値がfalsyである。
その他全ての値が、truthyである。

  • false
  • 0-00n (ゼロ)
  • "" (空文字列)
  • null
  • undefined
  • NaN


論理AND (&&)

論理AND演算子 (&&) は、左から右へ評価し、最初のfalsyなオペランドを返す。
全てのオペランドがtruthyの場合は、最後のオペランドの値を返す。

 console.log(5 && 10);            // 10 (5 は truthy、最後の値を返す)
 console.log(0 && 10);            // 0 (0 は falsy、0 を返す)
 console.log(null && "hello");    // null (null は falsy、null を返す)
 console.log("hello" && "world"); // "world" (両方 truthy、最後の値を返す)
 console.log("" && "world");      // "" ("" は falsy、"" を返す)


  • 短絡評価の例
    &&の左辺がfalsyの場合、右辺は評価されない。
     function getValue() {
        console.log("getValue called");
        return 10;
     }
     
     const result = false && getValue();  // getValue は呼ばれない (短絡評価)
     console.log(result);                 // false
    


論理OR (||)

論理OR演算子 (||) は、左から右へ評価し、最初のtruthyなオペランドを返す。
全てのオペランドがfalsyの場合は、最後のオペランドの値を返す。

 console.log(5 || 10);            // 5 (5はtruthy、5を返す)
 console.log(0 || 10);            // 10 (0はfalsy、次を評価して10を返す)
 console.log(null || "hello");    // "hello"
 console.log("" || null || 0);    // 0 (全てfalsy、最後の値を返す)


短絡評価による代替値の設定を以下に示す。

 const user = getUserFromAPI() || { name: "Guest" };
 
 const apiResult = callAPI() || getLocalData() || getDefaultData();


論理NOT (!)

論理NOT演算子 (!) は、オペランドの真偽値を反転する。
値は最初にbooleanに変換され、その逆の値を返す。

 console.log(!true);              // false
 console.log(!5);                 // false (5 は truthy)
 console.log(!"hello");           // false ("hello" は truthy)
 console.log(!0);                 // true (0 は falsy)
 console.log(!null);              // true (null は falsy)
 console.log(!undefined);         // true (undefined は falsy)


2重否定 !! を使用することで、任意の値を boolean に変換 (booleanへの明示的変換) できる。

 console.log(!!5);                // true
 console.log(!!"hello");          // true
 console.log(!!0);                // false
 console.log(!!null);             // false
 console.log(!!undefined);        // false
 console.log(!!"");               // false


論理代入演算子 (&&=, /, ||=, /, ??=)

論理代入演算子は、ES2021で導入された演算子であり、論理演算と代入を組み合わせたものである。
通常の代入とは異なり、条件を満たした場合にのみ代入が行われる。

  • 論理積代入 (&&=)
    左辺がtruthyの場合にのみ、右辺を左辺に代入する。
     let count = 5;
     count &&= 10;        // countがtruthyなので、10を代入
     console.log(count);  // 10
     
     let value = 0;
     value &&= 20;        // valueがfalsyなので代入されない
     console.log(value);  // 0
    

  • 論理和代入 (||=)
    左辺が falsy の場合にのみ、右辺を左辺に代入する。
     let name = "";
     name ||= "Guest";        // nameがfalsyなので、"Guest"を代入
     console.log(name);       // "Guest"
     
     let existing = "John";
     existing ||= "Guest";    // existingがtruthyなので代入されない
     console.log(existing);   // "John"
    

  • Null合体代入 (??=)
    左辺がnull または undefined の場合にのみ、右辺を左辺に代入する。
     let status = undefined;
     status ??= "pending";  // undefinedなので、"pending"を代入
     console.log(status);   // "pending"
     
     let count = 0;
     count ??= 10;          // 0はnull / undefinedではないので代入されない
     console.log(count);    // 0
    



短絡評価

短絡評価 (Short-circuit Evaluation) とは、論理式の左辺を評価した時点で結果が確定した場合に、右辺の評価をスキップする仕組みである。

  • && 演算子の短絡評価
    左辺がfalsyの場合、右辺は評価されない。

  • || 演算子の短絡評価
    左辺がtruthyの場合、右辺は評価されない。
     let sideEffect = false;
     
     // 左辺がfalseなので右辺は評価されない
     false && (sideEffect = true);
     console.log(sideEffect);  // false
     
     // 左辺がtrueなので右辺は評価されない
     true || (sideEffect = true);
     console.log(sideEffect);  // false
    


実用的なパターン

短絡評価を活用した実用的なコードパターンを以下に示す。

  • デフォルト値の設定 (ES5以前のパターン)
     const username = getUserInput() || "Anonymous";
     const timeout = getTimeout() || 5000;
     // 注意: getTimeout() が 0 を返す場合、5000 に上書きされてしまう
    

  • 条件付き実行パターン
     const user = getUserData();
     
     // user が truthy の場合のみ実行
     user && console.log("User logged in");
     
     // メソッドの存在確認後に実行
     user && user.save && user.save();
    

  • パフォーマンスを考慮した条件付き処理
     const hasPermission = checkPermission();
    
     // 許可がない場合、expensiveOperation は実行されない
     hasPermission && expensiveOperation();
    



Null合体演算子 (??)

Null合体演算子 (??) は、ES2020で導入された演算子である。

左辺がnullまたはundefinedの場合にのみ右辺を返し、それ以外の場合は左辺をそのまま返す。
0、""、false等のfalsy値を有効な値として扱う点が || との大きな違いである。

 console.log(null ?? "default");      // "default"
 console.log(undefined ?? "default"); // "default"
 console.log(0 ?? "default");         // 0 (0 は有効な値)
 console.log("" ?? "default");        // "" ("" は有効な値)
 console.log(false ?? "default");     // false (false は有効な値)


||との違い

|| はfalsy値 (0、""、false、null、undefined 等) で右辺を評価するが、?? はnullとundefinedのみで右辺を評価する。

|| と ?? の動作の違い
左辺の値 || の結果 ?? の結果 説明
null デフォルト値 デフォルト値 どちらも右辺を返す
undefined デフォルト値 デフォルト値 どちらも右辺を返す
0 デフォルト値 0 ?? は 0 を有効な値として扱う
"" (空文字列) デフォルト値 "" ?? は "" を有効な値として扱う
false デフォルト値 false ?? は false を有効な値として扱う
"hello" "hello" "hello" どちらも左辺を返す
42 42 42 どちらも左辺を返す


実用例を以下に示す。

 // || を使う場合: 0が上書きされる問題がある
 const timeout = getTimeout() || 5000;       // getTimeout() が0を返すと5000になる
 
 // ?? を使う場合: 0が有効な値として扱われる
 const timeout = getTimeout() ?? 5000;       // getTimeout() が0を返しても0のまま
 
 // "" (空文字列) の扱いの違い
 const message = getMessage() || "Unknown";  // "" は上書きされる
 const message = getMessage() ?? "Unknown";  // "" は有効な値として扱われる


演算子の優先順位

??&&|| と直接組み合わせて使用することができない。
直接組み合わせると構文エラーとなるため、括弧を使用して優先順位を明示する必要がある。

 // 構文エラーとなる例
 // const result = value1 && value2 ?? value3;  // SyntaxError
 // const result = value1 || value2 ?? value3;  // SyntaxError
 
 // 正しい記述: 括弧で優先順位を明示する
 const result = (value1 && value2) ?? value3;
 const result = (value1 || value2) ?? value3;



オプショナルチェーン (?.)

オプショナルチェーン演算子 (?.) は、ES2020で導入された演算子である。
ネストされたオブジェクトプロパティへのアクセス時に、途中で null または undefined が現れてもエラーをスローせず、undefined を返す。

プロパティアクセス

?. を使用することにより、null / undefinedチェックなしに安全にプロパティへアクセスできる。

 const user = { profile: { name: "John" } };
 
 // ?. を使わない場合: null チェックが必要
 const name1 = user && user.profile && user.profile.name;
 
 // ?. を使う場合: 簡潔に記述できる
 const name2 = user?.profile?.name;
 console.log(name2);  // "John"
 
 const empty = null;
 console.log(empty?.profile?.name);  // undefined (エラーなし)


  • ネストしたプロパティへのアクセス
    複数の ?. を連鎖させて使用できる。
     const user = getUserData();
     
     // null/undefined チェック付きで深くアクセス
     const city = user?.address?.city?.name;
     const zipCode = user?.address?.zipCode;
    

  • 配列インデックスへのアクセス
     const items = [1, 2, 3];
     console.log(items?.[0]);   // 1
     
     const empty = null;
     console.log(empty?.[0]);   // undefined (エラーなし)
    


メソッド呼び出し

?. を使用することにより、メソッドが存在するかどうかを確認してから安全に呼び出せる。

 const user = getUserData();
 
 // save メソッドが存在する場合のみ呼び出す
 user?.save?.();
 
 // コールバック関数の安全な呼び出し
 const callback = getCallback();
 callback?.();
 
 // 引数付きのメソッド呼び出し
 user?.updateProfile?.({ name: "Alice" });


??との組み合わせ

オプショナルチェーンがundefinedを返した場合、?? を使用してデフォルト値を指定できる。
この組み合わせにより、安全なプロパティアクセスとデフォルト値の設定を同時に実現できる。

 const user = getUserData();
 
 // userがnull / undefinedでも安全にアクセスし、デフォルト値を設定する
 const name = user?.name ?? "Anonymous";
 const age  = user?.profile?.age ?? 0;
 const city = user?.address?.city?.name ?? "Unknown";
 
 console.log(name);  // "Anonymous" (userがnull / undefinedの場合)
 console.log(age);   // 0 (ageがnull / undefinedの場合)



Object.is

Object.is() メソッドは、2つの値が同じ値であるかどうかを判定するメソッドである。
=== による厳密等価比較に似ているが、NaN と -0 / +0 の扱いが異なる。

===との違い

Object.is()=== の主な違いは以下の2点である。

  • NaNの扱い
    • NaN === NaN はfalseを返す。
    • Object.is(NaN, NaN) はtrueを返す。

  • +0 と -0 の扱い
    • +0 === -0 はtrueを返す。
    • Object.is(+0, -0) はfalseを返す。


 // NaN の比較
 console.log(NaN === NaN);           // false
 console.log(Object.is(NaN, NaN));   // true
 
 // +0 と -0 の比較
 console.log(0 === -0);              // true
 console.log(Object.is(0, -0));      // false
 console.log(Object.is(+0, -0));     // false
 
 // その他の値 (=== と同じ動作)
 console.log(5 === 5);               // true
 console.log(Object.is(5, 5));       // true
 
 console.log(5 === "5");             // false
 console.log(Object.is(5, "5"));     // false
 
 console.log(null === null);         // true
 console.log(Object.is(null, null)); // true


  • 使用すべき場面
    • NaNであるかどうかを正確に判定したい場合
      ただし、Number.isNaN(value) の使用も検討すること。
    • アルゴリズムの実装等で、+0 と -0 を区別する必要がある場合
    • React等のフレームワークが内部的に使用する同一性チェックのような用途

  • NaNの判定方法の比較
     const value = NaN;
     
     console.log(value === NaN);          // false (=== では NaN を検出できない)
     console.log(Object.is(value, NaN));  // true
     console.log(Number.isNaN(value));    // true (推奨される方法)
    



関連情報