空の配列も空のオブジェクトも真なので、if (value) では判定できません。それぞれの正しい調べ方、null と空を分けるべき理由、1つの関数にまとめる形まで実行して確かめます。
答え
種類ごとに見るところが違います。
| 種類 |
調べ方 |
| 配列 |
list.length === 0 |
| 文字列 |
text.length === 0 |
| オブジェクト |
Object.keys(obj).length === 0 |
Map / Set |
map.size === 0 |
null / undefined |
value == null |
example.js
console.log([].length === 0);
console.log(''.length === 0);
console.log(Object.keys({}).length === 0);
console.log(new Map().size === 0, new Set().size === 0);
出力書き換えて実行できます
true
true
true
true true
javascript
console.log(Boolean([]));
console.log(Boolean({}));
console.log(Boolean(new Map()));
true
true
true
if (value) では判定できない
これがいちばんの落とし穴です。
example.js
for (const value of [false, 0, '', null, undefined, NaN]) {
console.log(JSON.stringify(value), Boolean(value));
}
出力書き換えて実行できます
false false
0 false
"" false
null false
undefined false
null false
example.js
const list = [];
console.log(list ? 'ある' : 'ない');
console.log(list.length > 0 ? '中身がある' : '空');
出力書き換えて実行できます
ある
空
空の配列も空のオブジェクトも「真」です。
オブジェクトはすべて真になり、中身は見られません。
偽になるのは、この6つだけです。
example.js
const withUndefined = { a: undefined };
const inherited = Object.create({ base: 1 });
const symbolOnly = { [Symbol('s')]: 1 };
console.log(Object.keys(withUndefined).length);
console.log(Object.keys(inherited).length);
console.log(Object.keys(symbolOnly).length);
console.log(Object.getOwnPropertySymbols(symbolOnly).length);
出力書き換えて実行できます
1
0
0
1
javascript
console.log(JSON.stringify({ a: undefined }));
console.log(JSON.stringify({ a: undefined }) === '{}');
{}
true
NaN は JSON にすると null と書かれます。ここでの最後の行は NaN の結果です。
だから if (list) は「配列があるか」しか見ていません。
中身が空かどうかは、別に調べる必要があります。
example.js
function message(list) {
if (list == null) return '読み込み中';
if (list.length === 0) return '該当なし';
return `${list.length}件`;
}
console.log(message(null));
console.log(message([]));
console.log(message([1, 2]));
出力書き換えて実行できます
読み込み中
該当なし
2件
example.js
function isEmpty(value) {
if (value == null) return true;
if (typeof value === 'string' || Array.isArray(value)) return value.length === 0;
if (value instanceof Map || value instanceof Set) return value.size === 0;
if (typeof value === 'object') return Object.keys(value).length === 0;
return false;
}
for (const value of [null, undefined, '', [], {}, new Map(), 0, [1], { a: 1 }]) {
console.log(isEmpty(value), '|', Array.isArray(value) ? JSON.stringify(value) : String(value));
}
出力書き換えて実行できます
true | null
true | undefined
true |
true | []
true | [object Object]
true | [object Map]
false | 0
false | [1]
false | [object Object]
オブジェクトの空判定で気をつけること
Object.keys() が数えるのは自分の・列挙できる・文字列の鍵だけです。
example.js
for (const value of [null, undefined, [], 0, '']) {
console.log(
JSON.stringify(value),
'| ?? ->',
JSON.stringify(value ?? []),
'| || ->',
JSON.stringify(value || []),
);
}
出力書き換えて実行できます
null | ?? -> [] | || -> []
undefined | ?? -> [] | || -> []
[] | ?? -> [] | || -> []
0 | ?? -> 0 | || -> []
"" | ?? -> "" | || -> []
javascript
const holed = [, ,];
console.log(holed.length);
console.log(holed.filter(() => true).length);
console.log(Object.keys(holed).length);
2
0
0
undefined が入っていても、鍵はある(空ではない)
- 受け継いだ鍵は数えない
Symbol の鍵は数えない(あるのに空と判定される)
JSON.stringify() で見ると、また違う結果になります。
example.js
const form = { name: '', tags: [], note: null, count: 0 };
const missing = Object.entries(form)
.filter(([, value]) =>
value == null ||
(typeof value === 'string' && value.trim() === '') ||
(Array.isArray(value) && value.length === 0),
)
.map(([key]) => key);
console.log(missing);
出力書き換えて実行できます
[ 'name', 'tags', 'note' ]
javascript
const input = ' ';
console.log(input.length === 0);
console.log(input.trim().length === 0);
false
true
鍵はあるのに {} と出ます。
JSON.stringify() での空判定は結果が変わるので使わないでください。
→ Object.keys() / values() / entries()
null と「空」を分ける
この2つは、たいてい意味が違います。
| 状態 |
意味 |
null / undefined |
まだ来ていない・失敗した |
[] / {} |
来たが、0件だった |
画面の出し分けで効いてきます。
この3つを1つにまとめると、読み手に嘘をつくことになります。
「読み込み中」と「0件」は、利用者にとって全く違う情報です。
分ける必要が無い場面もあります。そのときは list?.length を見れば、null も空も一度に扱えます。
まとめて判定する
種類が混ざるものを受けるなら、1つの関数にします。
0 と false は「空」ではありません。 値として入っています。
ここを空に含めると、0件と0という値を区別できなくなります。
空に倒すときの ?? と ||
無いものを空配列にして進める書き方は便利ですが、演算子で結果が変わります。
配列を扱うだけなら差は出ませんが、0 や空文字が混ざる場所では変わります。
「無い」を空に倒すなら ??です。
→ ?. と ??
穴だけの配列は「空」ではない
length は2ですが、要素は1つもありません。
length だけで判定すると、空なのに空でないと出ます。
穴が混ざりうるなら、Object.keys(list).length を見てください。
→ Array.from() / Array.of() / new Array()
入力欄の未入力をまとめて挙げる
count: 0 は未入力に含めていません。
0 は入力された値です。ここを混ぜると、0 を入れた人に「未入力です」と言うことになります。
空白だけの文字列
入力欄の値を見るときは、trim() してから長さを見てください。
→ trim() / padStart() / padEnd() / repeat()
まとめ
if (value) では空を判定できない。 空の配列もオブジェクトも真
- 配列と文字列は
length、オブジェクトは Object.keys().length、Map / Set は size
Object.keys() はSymbol の鍵を数えない
JSON.stringify() で空判定をしない。undefined の鍵が消える
null(来ていない)と [](0件)は分けて扱う
- 空に倒すなら
|| ではなく ??
- 穴だけの配列は
length が0にならない
- 入力欄は
trim() してから長さを見る。0 を未入力に含めない