配列の存在チェック(空判定)について個人的に思うことです。
少しだけECMAScriptにも触れます。
配列の存在チェックどうしてる?
チェックの方法って結構バラバラですよね。
// array => []
if (array.length === 0) {...}
if (array[0]) {...} // falsyな値の時は意図しない動きに。詳しくはコメント欄へ。
if (array[0] !== void 0) {...}
if (array.length > 0) {...}
チェックはもうこれで良いんじゃないかな
if (array.length) {...}
if (!array.length) {...}
理由
- そもそも、
length
って名前が0以上の数値ってことが自明だから - JavaScriptでは、0が
false
、1以上はtrue
となるから
まとめ その1
早いですが、一旦まとめます。
別に大した話じゃなく、配列の存在チェックはarray.length
でなんら問題ないということです。
array.length === 0
とかが駄目という話ではないということは、念のために書いておきます。(可読性的な意味で)
伝えたいことはこれでほぼ全部なので、これ以降はおまけです。
根拠
配列の仕様について少しだけ触れます。
配列のインデックスについて
配列のインデックスについてはこのように定義されています。(一部抜粋)
An array index is an integer index whose numeric value i is in the range +0 ≤ i < 2^32 - 1. 1
配列インデックスの範囲は +0 ≤ i < 2^32 - 1
の範囲である。
つまり、マイナス値を取ることはないということです。
length
プロパティについて
配列のlength
プロパティはこのように定義されています。(一部抜粋)
Every Array object has a non-configurable "length" property whose value is always a nonnegative integer less than 2^32 2
- non-configurableなプロパティ3
- 2^32以下の非負な整数値
さらに、length
プロパティはWritableなので値を代入できます。
// array => ["a", "b"]
array.length // => 2
array.length = 3 // => 3
array.length = -1 // => Uncaught RangeError
注目していただきたいのは、マイナスを代入したときのエラーです。
なぜこうなるかは、やはり仕様を見ればわかります。
9.4.2.4 ArraySetLength ( A, Desc ) 4
The abstract operation ArraySetLength takes arguments A (an Array object) and Desc (a Property Descriptor). It performs the following steps when called:
A
は配列オブジェクトのことです。Desc
とはProperty Descriptorのことです。
Property Descriptorとは簡単にいうとオブジェクトプロパティへのメタ属性達のことであり、[[Value]]や[[Writable]]などが存在します。
配列の作成時には { [[Value]]: length, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }
が設定されています。
- If Desc.[[Value]] is absent, then
a. Return OrdinaryDefineOwnProperty(A, "length", Desc). - Let newLenDesc be a copy of Desc.
- Let newLen be ? ToUint32(Desc.[[Value]]).
- Let numberLen be ? ToNumber(Desc.[[Value]]).
- If newLen ≠ numberLen, throw a RangeError exception.
...(省略)
(訳)
- もし Desc.[[Value]] がなければ
a. OrdinaryDefineOwnProperty(A, "length", Desc). を返す - newLenDesc は Desc のコピーとする
- newLen は ? ToUint32(Desc.[[Value]]) とする
- numberLen は ? ToNumber(Desc.[[Value]]).
- もし newLen ≠ numberLen であれば, RangeError exception. をスローする
[[Value]]は属性名です。プロパティに対してgetで取得された値を指します。
array.length = -1
をトレースするとこうなります。
3.で-1
を渡すのでToUint32(-1) // => 4294967295
5となります。
4.で-1
を渡すのでToNumber(-1) // => -1
となります。
5.で4294967295 ≠ -1
なので、RangeError exception. をスローします。
まとめ その2
根拠で確認した内容から絶対0未満にならないことがわかりました。
仕様という根拠があればこれでいい気がしてきませんでしょうか?
if (array.length) {...}
以上、配列の存在チェック(空判定)は if (array.length) {...} でいいよって話でした。
さらにおまけ
例えば、こんなお行儀の悪いことをしている人がいたとしたら length
では検知できません。
const array = []
array["hoge"] = "hoge"
console.log(array) // => [hoge: "hoge"]
console.log(array[0]) // => undefind
console.log(array.length) // => 0
// pushなどをしてあげれば、lengthの値も変更される
array.push("fuga")
console.log(array) // => ["fuga", hoge: "hoge"]
console.log(array[0]) // => "fuga"
console.log(array.length) // => 1
これは、配列はオブジェクトであり、Objectを継承したものが配列であるからです。
key-valueの形式で値を保持することができるのですが、まあ気持ちが悪いですね。
因みに、-1
もkeyに設定できます。
const array = []
array[-1] = "hoge"
console.log(array) // => [-1: "hoge"]
console.log(array[0]) // => undefind
console.log(array.length) // => 0
console.log(array[-1]) // => "hoge"
配列のインデックスについてで述べたとおり、配列で指定できるインデックスの範囲外であるためkeyとして設定されます。
チェックする方法はあります。
const array = []
array["hoge"] = "hoge"
array[0] = "fuga"
Object.keys(array) // => ["0", "hoge"]
Object.keys(array).length // => 2
Object.keys
を使用してkeyを列挙することでチェック可能です。
参考
ECMAScript® 2021 Language Specification
-
ECMAScript® 2021 Language Specification#sec-array-exotic-objects ↩
-
プロパティの削除が出来ないなどの意味を持ちます。 ↩
-
ECMAScript® 2021 Language Specification#sec-arraysetlength ↩
-
ToUint32についてなにをやっているかは、仕様を見ていただくか、JavaScriptのビット演算の仕組みを理解する - 風と宇宙とプログラム こちらのブログが大変わかりやすいです。 ↩