【JavaScript】演算子の使い方まとめ|=== vs ==・?? vs ||・?.オプショナルチェーン・論理代入・優先順位まで解説

JavaScript には算術・比較・論理・代入など様々な演算子があります。なかでも ===== の違いや、??(Null合体)と || の使い分け?.(オプショナルチェーン)は、実務で頻繁に登場するにもかかわらず誤解されやすい演算子です。

この記事では各演算子カテゴリの概要を整理したうえで、特に使い方を間違えやすいポイントと、ES2020 以降に追加された新しい演算子を中心に解説します。

スポンサーリンク

JavaScript の演算子カテゴリ一覧

カテゴリ 主な演算子
算術演算子 + - * / % ** 2 ** 101024
比較演算子 === !== == != < > <= >= 1 === "1"false
論理演算子 && || ! ?? null ?? "default""default"
代入演算子 = += -= *= /= %= **= &&= ||= ??= x &&= newVal
ビット演算子 & | ^ ~ << >> >>> 5 & 31
単項演算子 typeof instanceof void delete in typeof null"object"
三項演算子 ? : x > 0 ? "正" : "以下"
オプショナルチェーン ?. obj?.prop
スプレッド / レスト ... [...arr1, ...arr2]

=== と == の違い:厳密等値を使うべき理由

===(厳密等値)と ==(緩やかな等値)の違いは、型変換を行うかどうかです。== は型が異なると暗黙的な型変換を行ってから比較するため、予期しない結果になることがあります。

== の予期しない挙動
// == は型変換を行うため直感と異なる結果になりやすい
console.log(1   == '1');    // true  (数値→文字列に変換して比較)
console.log(0   == false);  // true  (false→0に変換)
console.log(0   == '');     // true  (空文字→0に変換)
console.log(''  == false);  // true  (false→''に変換)
console.log(null == undefined); // true  (特例ルール)
console.log([]  == false);  // true  ([]→''→0, false→0)
console.log([]  == ![]);    // true  (!=[] = false, []==false)

// === は型変換しないため結果が直感的
console.log(1   === '1');   // false (型が違う)
console.log(0   === false); // false
console.log(null === undefined); // false
実務では常に === を使う:== の型変換ルールは複雑で、全ケースを記憶するのは現実的ではありません。null と undefined の両方をまとめてチェックしたいときvalue == null)を除き、常に ===!== を使うのが推奨されています(ESLint の eqeqeq ルールも同様)。
null/undefined を == で一括チェックする唯一の例外
// value が null または undefined のどちらかであれば true
// これは意図的に == を使う唯一のケース
function isEmpty(value) {
  return value == null; // null === null || undefined === null はどちらも真
}
console.log(isEmpty(null));      // true
console.log(isEmpty(undefined)); // true
console.log(isEmpty(0));         // false(0はnullでもundefinedでもない)
console.log(isEmpty(''));        // false

比較演算子の使い方

比較演算子
// 数値の大小比較
console.log(5 > 3);    // true
console.log(5 >= 5);   // true
console.log(3 < 5);    // true
console.log(3 <= 2);   // false

// 文字列の比較(辞書順・Unicode順)
console.log('apple' < 'banana'); // true
console.log('B' < 'a');         // true (大文字のコードポイントが小さい)

// NaN との比較はすべて false
console.log(NaN === NaN); // false (NaN は自分自身とも等しくない!)
console.log(Number.isNaN(NaN)); // true (NaN の判定には isNaN を使う)
NaN は自分自身と等しくない:NaN === NaNfalse です。これは IEEE 754 の仕様によるものです。NaN かどうかを判定するには Number.isNaN(value) を使ってください。isNaN()(グローバル関数)は文字列を数値に変換してから判定するため、isNaN("hello")true になるなど挙動が異なります。必ず Number.isNaN() を使ってください。

論理演算子と短絡評価(&&・||・??)

論理演算子はすべて短絡評価(Short-circuit evaluation)を行います。条件が確定した時点で残りの評価をスキップします。

&& と || の短絡評価
// && は左辺が falsy なら左辺を返す(右辺は評価しない)
console.log(false && 'hello'); // false
console.log(null  && 'hello'); // null
console.log(1     && 'hello'); // 'hello'(左辺が truthy → 右辺を返す)

// || は左辺が truthy なら左辺を返す(右辺は評価しない)
console.log('hi'  || 'default'); // 'hi'
console.log(null  || 'default'); // 'default'
console.log(0     || 'default'); // 'default'(0 は falsy)
console.log(''    || 'default'); // 'default'(空文字は falsy)

// 返り値は boolean ではなく、評価を止めた時点の値そのもの
const name = null;
const displayName = name || '名無し'; // '名無し'

?? (Null合体演算子)vs || の使い分け

??(Null 合体演算子)は ES2020 で追加されました。|| と似ていますが、左辺が null または undefined の場合だけ右辺を返す点が異なります。

?? vs || の違い
// || は falsy(0・''・false など)でも右辺に切り替わる
const score1 = 0  || 100;  // 100 ← 意図しない上書き!0は有効な値なのに
const label1 = '' || '未設定'; // '未設定' ← 空文字も有効な場合に困る

// ?? は null / undefined だけに反応する
const score2 = 0    ?? 100;     // 0    ← 0はそのまま保持
const score3 = null ?? 100;     // 100  ← nullのときだけデフォルト
const label2 = ''   ?? '未設定'; // ''   ← 空文字はそのまま
const label3 = undefined ?? '未設定'; // '未設定'
使い分けの基準:0""false も有効な値として扱いたい」→ ?? を使う。「falsy な値はすべてデフォルト値に置き換えたい」→ || を使う。API レスポンスやユーザー入力のデフォルト設定には ?? が適していることが多いです。

?. オプショナルチェーン演算子

?. は ES2020 で追加された演算子で、プロパティや関数が null/undefined の場合にエラーを投げず undefined を返します。

?. の使い方
const user = {
  name: '太郎',
  address: {
    city: '東京'
  }
};

// 従来の書き方(ネストが深いと冗長)
const city1 = user && user.address && user.address.city; // '東京'
const zip1  = user && user.address && user.address.zip;  // undefined

// ?. を使った書き方(短く読みやすい)
const city2 = user?.address?.city;    // '東京'
const zip2  = user?.address?.zip;     // undefined(エラーにならない)
const phone = user?.contact?.phone;   // undefined(contact が存在しない)

// null / undefined のオブジェクトに ?. を使っても安全
const nullObj = null;
console.log(nullObj?.prop);            // undefined(TypeError にならない)

// メソッド呼び出し
const arr = null;
console.log(arr?.map((x) => x * 2));  // undefined

// 配列要素へのアクセス
const list = null;
console.log(list?.[0]);                // undefined
?? との組み合わせ(フォールバック付き)
// ?. と ?? を組み合わせると「存在すれば値を返し、なければデフォルト値」が1行で書ける
const user2 = null;
const cityName = user2?.address?.city ?? '未設定'; // '未設定'

const config = { timeout: 0 };
const timeout = config?.timeout ?? 3000; // 0 (??なので0はそのまま)

論理代入演算子(&&=・||=・??=)ES2021

ES2021 で追加された3つの論理代入演算子は、論理演算と代入を組み合わせた省略形です。

論理代入演算子の使い方
// &&= : 左辺が truthy のときだけ右辺を代入
let a = 1;
a &&= 10;   // a は truthy → a = 10
let b = 0;
b &&= 10;   // b は falsy → 代入されない(b = 0 のまま)

// ||= : 左辺が falsy のときだけ右辺を代入(デフォルト値の設定に便利)
let name = null;
name ||= '名無し'; // null は falsy → name = '名無し'
let score = 0;
score ||= 100;     // 0 は falsy → score = 100 ← 0も上書きされるので注意

// ??= : 左辺が null / undefined のときだけ右辺を代入
let x = null;
x ??= 42;    // null → x = 42
let y = 0;
y ??= 42;    // 0 は null でも undefined でもない → y = 0 のまま

// ??= はオブジェクトの初期化に特に便利
const cache = {};
cache.data ??= [];
cache.data.push('item'); // キャッシュが未初期化なら配列を作成してから追加
論理代入演算子の短絡評価:x &&= valuex && (x = value) と等価です。左辺が条件を満たさない場合は右辺の評価も代入も行われません。単純な x = x && value とは、右辺を評価するかどうかの点で異なります。

typeof・instanceof・in 演算子

typeof の使い方と落とし穴
// typeof は値の型を文字列で返す
console.log(typeof 42);          // 'number'
console.log(typeof 'hello');     // 'string'
console.log(typeof true);        // 'boolean'
console.log(typeof undefined);   // 'undefined'
console.log(typeof Symbol());    // 'symbol'
console.log(typeof 42n);         // 'bigint'
console.log(typeof function(){}); // 'function'
console.log(typeof {});          // 'object'
console.log(typeof []);          // 'object' ← 配列も 'object'!
console.log(typeof null);        // 'object' ← これはJSの有名なバグ

// 配列かどうかは Array.isArray() で確認
console.log(Array.isArray([]));  // true
// null かどうかは === null で確認
console.log(null === null);      // true
instanceof と in
// instanceof: オブジェクトがコンストラクタのインスタンスかどうか
console.log([] instanceof Array);   // true
console.log([] instanceof Object);  // true (Array は Object のサブクラス)
console.log('hi' instanceof String); // false (プリミティブには使えない)

// in: オブジェクトにプロパティが存在するか(継承チェーンも含む)
const obj = { name: '太郎', age: 25 };
console.log('name' in obj);         // true
console.log('email' in obj);        // false
console.log('toString' in obj);     // true (Object.prototypeから継承)

// hasOwnProperty で継承を除いた自身のプロパティだけ確認
console.log(Object.hasOwn(obj, 'name'));     // true
console.log(Object.hasOwn(obj, 'toString')); // false

複合代入演算子(+=・-= など)

複合代入演算子
let n = 10;
n += 5;   // n = n + 5  → 15
n -= 3;   // n = n - 3  → 12
n *= 2;   // n = n * 2  → 24
n /= 4;   // n = n / 4  → 6
n %= 4;   // n = n % 4  → 2
n **= 3;  // n = n ** 3 → 8

// 文字列への += も使える
let str = 'Hello';
str += ', World'; // 'Hello, World'

演算子の優先順位と括弧による制御

演算子には優先順位(precedence)があり、高いものから先に評価されます。迷ったら括弧を使って明示的に優先順位を指定するのが最も安全です。

優先度(高→低) 演算子の種類
19 グループ化 ()
17 メンバーアクセス・関数呼び出し obj.propfn()
16 new(引数あり) new Foo()
15 後置インクリメント x++x--
14 単項演算子 !・typeof・void・delete
13 べき乗 **
12 乗除・剰余 * / %
11 加減 + -
9 比較 < > <= >= instanceof in
8 等値 === !== == !=
5 論理 AND &&
4 論理 OR・Null合体 ||・??
3 三項演算子 ? :
2 代入 = += -= ??= など
優先順位の落とし穴と括弧による解決
// ?? と || / && の混在はエラーになる(意図が曖昧なため)
// a ?? b || c  → SyntaxError(括弧が必要)
const result1 = (a ?? b) || c;  // OK: ?? を先に評価
const result2 = a ?? (b || c);  // OK: || を先に評価

// 算術と文字列結合の混在
console.log(2 + 3 + '円');    // '5円'  (2+3=5→'5'+'円')
console.log('合計: ' + 2 + 3); // '合計: 23' ← 意図しない結果
console.log('合計: ' + (2 + 3)); // '合計: 5' ← 括弧で制御

// 前置・後置インクリメントの違い
let i = 5;
console.log(i++); // 5 (現在の値を返してからインクリメント)
console.log(i);   // 6
console.log(++i); // 7 (インクリメントしてから値を返す)
優先順位より括弧で意図を明示:優先順位の全ルールを記憶するより、複雑な式には括弧を使って意図を明示するほうが読みやすく保守しやすいコードになります。ESLint の no-mixed-operators ルールを有効にすると、演算子が混在している箇所を警告してくれます。

よくある質問

Qtypeof null が “object” になるのはなぜですか?
AJavaScript の初期実装に存在するバグで、後方互換性のために現在も修正されていません。null のチェックは必ず value === null で行ってください。typeof value === "object" && value !== null のように typeof とnullチェックを組み合わせると、オブジェクト型(配列・オブジェクト・Date など)かどうかを正確に判定できます。
Q&&= と ||= はどんな場面で使いますか?
A&&= は「すでに値が存在する場合だけ更新する」パターンに便利です(例:user &&= { ...user, name: newName })。||= は「未設定なら初期値を入れる」パターンに使えますが、0""も上書きするため、それが問題になる場合は ??= を使ってください。
Q?. と && によるガード節の違いは何ですか?
A動作は似ていますが読みやすさが大きく異なります。a && a.b && a.b.c よりも a?.b?.c のほうが圧倒的にシンプルです。返り値も同じ(左辺が nullish なら undefined)ですが、&& は左辺が 0""false でも停止するため、意図しない挙動になる場合があります。?.null/undefined のときだけ停止します。
Qinstanceof の落とし穴はありますか?
Ainstanceof はプロトタイプチェーンを辿るため、iframe や別の実行コンテキストからの配列・オブジェクトには正しく機能しないことがあります。配列の判定は必ず Array.isArray() を使ってください。また、プリミティブ値(文字列・数値など)に使うと常に false になります。
Q演算子の優先順位を全て覚える必要がありますか?
A覚える必要はありません。代わりに、複数の演算子が混在する式には括弧を使って意図を明示することを習慣にしてください。括弧は優先順位を最高にするだけでなく、コードを読む人への「ここをまとめて計算する」というメッセージにもなります。

まとめ

JavaScript の演算子の重要ポイントをまとめます。

  • 比較は常に ===!== を使う(null == undefined の一括チェックのみ == の例外)
  • 0""false も有効な値なら ??(Null合体)、falsy 全体を置き換えるなら ||
  • ?. オプショナルチェーンでネストしたプロパティアクセスを安全に短く書ける
  • ??= は null/undefined のときだけ代入する最もクリーンなデフォルト値設定
  • typeof null === "object" はバグ。null チェックは === null
  • 複雑な式は優先順位を覚えるより括弧で意図を明示する

真偽値の反転(! 演算子)については【JavaScript】真偽値を反転させる方法、三項演算子の詳しい使い方は【JavaScript】三項演算子の使い方もあわせてご覧ください。