[JavaScript] 配列の存在チェック（空判定）は if (array.length) {...} でいいよって話

配列の存在チェック（空判定）について個人的に思うことです。
少しだけECMAScriptにも触れます。

## 配列の存在チェックどうしてる？

チェックの方法って結構バラバラですよね。

```js:配列の存在チェック例
// array => []
if (array.length === 0) {...}
if (array[0]) {...} // falsyな値の時は意図しない動きに。詳しくはコメント欄へ。
if (array[0] !== void 0) {...}
if (array.length > 0) {...}
```

## チェックはもうこれで良いんじゃないかな

```js
if (array.length) {...}
if (!array.length) {...}
```

## 理由
1. そもそも、`length`って名前が0以上の数値ってことが自明だから
1. 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]


[^1]: [ECMAScript® 2021 Language Specification#sec-object-type](https://tc39.es/ecma262/#sec-object-type)

配列インデックスの範囲は `+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]

[^2]: [ECMAScript® 2021 Language Specification#sec-array-exotic-objects](https://tc39.es/ecma262/#sec-array-exotic-objects)

- non-configurableなプロパティ[^3]
- **2^32以下の非負な整数値**

[^3]: プロパティの削除が出来ないなどの意味を持ちます。
 
さらに、`length`プロパティはWritableなので値を代入できます。

```js
// 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:

[^4]: [ECMAScript® 2021 Language Specification#sec-arraysetlength](https://tc39.es/ecma262/#sec-arraysetlength)

`A` は配列オブジェクトのことです。`Desc` とはProperty Descriptorのことです。
Property Descriptorとは簡単にいうとオブジェクトプロパティへのメタ属性達のことであり、[[Value]]や[[Writable]]などが存在します。
配列の作成時には `{ [[Value]]: length, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }` が設定されています。

> 
1. If Desc.[[Value]] is absent, then  
 a. Return OrdinaryDefineOwnProperty(A, "length", Desc).
1. Let newLenDesc be a copy of Desc.
1. Let newLen be ? ToUint32(Desc.[[Value]]).
1. Let numberLen be ? ToNumber(Desc.[[Value]]).
1. If newLen ≠ numberLen, **throw a RangeError exception.**  
...(省略)


（訳）
> 
1. もし Desc.[[Value]] がなければ  
    a. OrdinaryDefineOwnProperty(A, "length", Desc). を返す
1. newLenDesc は Desc のコピーとする
1. newLen は ? ToUint32(Desc.[[Value]]) とする
1. numberLen は ? ToNumber(Desc.[[Value]]).
1. もし 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.** をスローします。

[^5]: ToUint32についてなにをやっているかは、[仕様](https://tc39.es/ecma262/#sec-touint32)を見ていただくか、[JavaScriptのビット演算の仕組みを理解する - 風と宇宙とプログラム](https://mindcat.hatenadiary.org/entry/20091119/1258651717) こちらのブログが大変わかりやすいです。



## まとめ その2
根拠で確認した内容から絶対0未満にならないことがわかりました。
仕様という根拠があればこれでいい気がしてきませんでしょうか？

```js
if (array.length) {...}
```

以上、配列の存在チェック（空判定）は if (array.length) {...} でいいよって話でした。

## さらにおまけ

例えば、こんなお行儀の悪いことをしている人がいたとしたら `length` では検知できません。

```js
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に設定できます。

```js
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"
```
[配列のインデックスについて](https://qiita.com/kozzzz/items/42108bc96b3a0e7c53d5#%E9%85%8D%E5%88%97%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)で述べたとおり、配列で指定できるインデックスの範囲外であるためkeyとして設定されます。

チェックする方法はあります。

```js
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](https://tc39.es/ecma262/)