ボタンを押すたびにメニューの開閉状態が切り替わる、チェックボックスのオン/オフを反転させる——こうした「状態のトグル」は 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 |
false・0・-0・0n(BigInt)・""(空文字)・null・undefined・NaN の8つが falsy です(0n と -0 を含めると8つ)。それ以外の値(空配列 []・空オブジェクト {}・文字列 "false" を含む)はすべて truthy です。!! 二重否定:反転ではなく boolean への型変換
!! は「否定の否定」、すなわち任意の値を boolean 型に変換するイディオムです。値を反転するのではなく、truthy/falsy を true/false に正確に変換したいときに使います。
// !! は値を 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 は クラスの付与/削除を自動でトグルする
// 内部で同様のフラグ反転が行われている
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)
! 演算子の返り値は常に true か false です。文字列や数値の変数に ! を使うと、その変数は boolean 型に変わります。文字列状態("active"/"inactive")を切り替えるには三項演算子かlookup オブジェクトを使ってください。2値以上の状態をトグルするパターン
! は true/false の2値しか扱えません。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 オブジェクトで次の状態を定義する
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'
type Status = "active" | "inactive" のようなUnion 型で定義すると、想定外の値の混入をコンパイル時に防げます。lookup オブジェクトの型も Record<Status, Status> で補完が効くようになります。よくある質問
[] はオブジェクトであり、JavaScript ではオブジェクトはすべて truthy です。Boolean([]) === true なので、![] === false になります。「空配列かどうか」を判定したい場合は array.length === 0 を使ってください。"false" は文字列です。JavaScript では空文字 "" 以外の文字列はすべて truthy のため、Boolean("false") === true となり、!"false" === false になります。文字列 "false" と boolean の false は別物です。!value は truthy/falsy で判定するため、0・""・null なども false 扱いになります。value !== false は 厳密に false との比較で、0・null などは false 扱いにしません。変数が確実に boolean 型であれば ! で問題ありませんが、型が混在する可能性がある場合は === false や === true で明示的に比較するほうが安全です。setState(prev => !prev) のように関数形式を使います。setState(!currentValue) ではなく関数形式を使うことで、非同期更新による stale state(古い値の参照)を防げます。flag ^= 1 は 0 → 1 → 0 を繰り返します。パフォーマンスが厳しいループ内や、ビット操作が自然なコンテキスト(ゲーム・信号処理)では使われますが、通常のアプリケーションコードでは読みにくいため flag = !flag または flag = flag === 0 ? 1 : 0 を使うほうが可読性に優れます。まとめ
JavaScript の真偽値反転・トグル実装のポイントをまとめます。
!は右辺を boolean に変換してから反転する。返り値は常にtrueかfalse!!は「反転」ではなく「boolean への型変換」。Boolean()と同等- フラグのトグルは
flag = !flagの1行で書ける - 文字列・数値の状態を
!でトグルしようとすると boolean 型に変わる落とし穴に注意 - 2値以上の状態循環には lookup オブジェクト or
% STATUSES.lengthのインデックス法を使う - 空配列
[]・空オブジェクト{}・文字列"false"はすべて truthy なため!を使うとfalseになる
truthy/falsy を活用した条件分岐の詳細は【JavaScript】if 文による条件分岐の書き方、クラスの切り替えと classList.toggle は【JavaScript】classList の使い方完全ガイドもあわせてご覧ください。