【JavaScript】真偽値を反転させる方法|! 演算子・!! 二重否定・toggle パターン・非ブール値の落とし穴まで解説

ボタンを押すたびにメニューの開閉状態が切り替わる、チェックボックスのオン/オフを反転させる——こうした「状態のトグル」は JavaScript の至るところで登場します。

中心となるのは !(論理否定)演算子です。一見シンプルですが、真偽値以外の値(文字列・数値・null など)に使ったときの挙動は意図しない結果を招くことがあります。

この記事では ! の正確な動作から、!! 二重否定との使い分け、実践的なトグルパターン、よくある落とし穴まで体系的に解説します。

スポンサーリンク

! 演算子(論理否定)の基本動作

! は右辺の値を まず boolean に変換してから反転させます。返り値は必ず true または false になります。

! 演算子の基本
console.log(!true);   // false
console.log(!false);  // true

// フラグ変数のトグル
let isOpen = false;
isOpen = !isOpen; // true
isOpen = !isOpen; // false(元に戻る)
元の値 ! を適用した結果 理由
true false true の否定
false true false の否定
0 true 0 は falsy → Boolean(0) = false → !false = true
1(0以外の数値) false 0以外は truthy → Boolean(1) = true → !true = false
""(空文字) true 空文字は falsy → !false = true
"hello" false 非空文字は truthy → !true = false
null true null は falsy
undefined true undefined は falsy
[](空配列) false 配列はオブジェクトのため truthy
{}(空オブジェクト) false オブジェクトはすべて truthy
JavaScript の falsy な値は 6 つだけ:false0-00n(BigInt)・""(空文字)・nullundefinedNaN の8つが falsy です(0n-0 を含めると8つ)。それ以外の値(空配列 []・空オブジェクト {}・文字列 "false" を含む)はすべて truthy です。

!! 二重否定:反転ではなく boolean への型変換

!! は「否定の否定」、すなわち任意の値を boolean 型に変換するイディオムです。値を反転するのではなく、truthy/falsy を true/false に正確に変換したいときに使います。

!! による boolean 変換
// !! は値を boolean 型に変換する(反転ではない)
console.log(!!0);          // false
console.log(!!1);          // true
console.log(!!'');         // false
console.log(!!'hello');    // true
console.log(!!null);       // false
console.log(!!undefined);  // false
console.log(!![]);         // true (空配列は truthy!)
console.log(!!{});         // true (空オブジェクトも truthy!)

// Boolean() 関数と同等
console.log(!!1 === Boolean(1)); // true
! !!
目的 真偽値を反転させる 任意の値を boolean に変換する
返り値の型 常に boolean 常に boolean
元の値への影響 反転した値を返す truthy/falsy を維持したまま boolean 化
主な用途 フラグのトグル、条件の反転 型チェック、boolean への正規化
使い分けの例
const count = 3;

// NG: count を「反転」したいわけではない
const hasItems = !count; // false ではなく true が欲しいのに false になってしまう

// OK: truthy/falsy を boolean に変換したい → !!
const hasItems2 = !!count; // true(countが0以外なのでtruthy → true)

// さらに明示的に書くなら Boolean() でも同じ
const hasItems3 = Boolean(count); // true
!!Boolean() はどちらを使う?動作は同一です。Boolean(value) は意図が明確で読みやすく、!!value は簡潔に書けます。チームのコーディング規約に合わせてください。TypeScript では型の絞り込み(type narrowing)として両方有効です。

フラグ変数のトグルパターン

UI 状態の切り替えは flag = !flag の1行で実装できます。

メニューの開閉トグル
let isMenuOpen = false;

const menuBtn  = document.getElementById('menuBtn');
const menu     = document.getElementById('menu');

menuBtn.addEventListener('click', () => {
  isMenuOpen = !isMenuOpen; // true ↔ false を交互に切り替え

  menu.hidden = !isMenuOpen;
  menuBtn.setAttribute('aria-expanded', String(isMenuOpen));
  menuBtn.textContent = isMenuOpen ? 'メニューを閉じる' : 'メニューを開く';
});
classList.toggle との組み合わせ
// classList.toggle は クラスの付与/削除を自動でトグルする
// 内部で同様のフラグ反転が行われている
const panel = document.getElementById('panel');

document.getElementById('toggleBtn').addEventListener('click', () => {
  panel.classList.toggle('is-open');

  // classList.toggle の戻り値はクラスが付与された(true)か削除された(false)か
  const isOpen = panel.classList.contains('is-open');
  document.getElementById('toggleBtn').setAttribute('aria-expanded', String(isOpen));
});
ダークモードのトグル
let isDark = localStorage.getItem('theme') === 'dark';

function applyTheme() {
  document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
  document.getElementById('themeBtn').textContent = isDark ? '☀️ ライト' : '? ダーク';
}

document.getElementById('themeBtn').addEventListener('click', () => {
  isDark = !isDark;
  localStorage.setItem('theme', isDark ? 'dark' : 'light');
  applyTheme();
});

applyTheme(); // 初期適用

非ブール値に ! を使うときの落とし穴

変数が必ずしも true/false でない場合、! の結果が直感と異なることがあります。

落とし穴:文字列・数値のトグルに ! を使う
// NG: 文字列の状態管理に ! を使う
let status = 'active'; // 'active' は truthy
status = !status;      // false(文字列ではなく boolean になってしまう!)

// NG: 数値カウンターに ! を使う
let count = 5;
count = !count; // false(期待した「5の反転」ではなく boolean になる)

// OK: 文字列状態のトグルは明示的に
status = (status === 'active') ? 'inactive' : 'active';

// OK: number の 0/1 トグルには XOR
let flag = 0;
flag ^= 1; // 0 → 1 → 0 のトグル(bitwise XOR)
! は必ず boolean を返す:! 演算子の返り値は常に truefalse です。文字列や数値の変数に ! を使うと、その変数は boolean 型に変わります。文字列状態("active"/"inactive")を切り替えるには三項演算子かlookup オブジェクトを使ってください。

2値以上の状態をトグルするパターン

! は true/false の2値しか扱えません。3値以上の状態を循環させるには別のアプローチが必要です。

3値ステータスの循環トグル
const STATUSES = ['todo', 'doing', 'done'];
let currentIndex = 0;

function nextStatus() {
  currentIndex = (currentIndex + 1) % STATUSES.length;
  return STATUSES[currentIndex];
}

console.log(nextStatus()); // 'doing'
console.log(nextStatus()); // 'done'
console.log(nextStatus()); // 'todo'(ループ)
lookup オブジェクトで状態遷移を管理
// lookup オブジェクトで次の状態を定義する
const nextState = {
  'active':   'inactive',
  'inactive': 'active',
};

let status = 'active';

function toggleStatus() {
  status = nextState[status] ?? status; // 未定義状態は変更しない
  return status;
}

console.log(toggleStatus()); // 'inactive'
console.log(toggleStatus()); // 'active'
状態管理は型安全に:TypeScript を使う場合、状態を type Status = "active" | "inactive" のようなUnion 型で定義すると、想定外の値の混入をコンパイル時に防げます。lookup オブジェクトの型も Record<Status, Status> で補完が効くようになります。

よくある質問

Q![] が false にならない理由は何ですか?
A空配列 [] はオブジェクトであり、JavaScript ではオブジェクトはすべて truthy です。Boolean([]) === true なので、![] === false になります。「空配列かどうか」を判定したい場合は array.length === 0 を使ってください。
Q!”false” はなぜ false になるのですか?
A"false" は文字列です。JavaScript では空文字 "" 以外の文字列はすべて truthy のため、Boolean("false") === true となり、!"false" === false になります。文字列 "false" と boolean の false は別物です。
Q! と !== false の使い分けは?
A!value は truthy/falsy で判定するため、0""null なども false 扱いになります。value !== false厳密に false との比較で、0null などは false 扱いにしません。変数が確実に boolean 型であれば ! で問題ありませんが、型が混在する可能性がある場合は === false=== true で明示的に比較するほうが安全です。
QReact の state でフラグをトグルするには?
AsetState(prev => !prev) のように関数形式を使います。setState(!currentValue) ではなく関数形式を使うことで、非同期更新による stale state(古い値の参照)を防げます。
Q^ (XOR)演算子で 0/1 をトグルする方法はいつ使いますか?
Aflag ^= 1010 を繰り返します。パフォーマンスが厳しいループ内や、ビット操作が自然なコンテキスト(ゲーム・信号処理)では使われますが、通常のアプリケーションコードでは読みにくいため flag = !flag または flag = flag === 0 ? 1 : 0 を使うほうが可読性に優れます。

まとめ

JavaScript の真偽値反転・トグル実装のポイントをまとめます。

  • ! は右辺を boolean に変換してから反転する。返り値は常に truefalse
  • !! は「反転」ではなく「boolean への型変換」。Boolean() と同等
  • フラグのトグルは flag = !flag の1行で書ける
  • 文字列・数値の状態を ! でトグルしようとすると boolean 型に変わる落とし穴に注意
  • 2値以上の状態循環には lookup オブジェクト or % STATUSES.length のインデックス法を使う
  • 空配列 []・空オブジェクト {}・文字列 "false" はすべて truthy なため ! を使うと false になる

truthy/falsy を活用した条件分岐の詳細は【JavaScript】if 文による条件分岐の書き方、クラスの切り替えと classList.toggle は【JavaScript】classList の使い方完全ガイドもあわせてご覧ください。