270
156

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-05-25

配列の存在チェック(空判定)について個人的に思うことです。
少しだけ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) {...}

理由

  1. そもそも、lengthって名前が0以上の数値ってことが自明だから
  2. 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 } が設定されています。

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

(訳)

  1. もし Desc.[[Value]] がなければ
    a. OrdinaryDefineOwnProperty(A, "length", Desc). を返す
  2. newLenDesc は Desc のコピーとする
  3. newLen は ? ToUint32(Desc.[[Value]]) とする
  4. numberLen は ? ToNumber(Desc.[[Value]]).
  5. もし newLen ≠ numberLen であれば, RangeError exception. をスローする

[[Value]]は属性名です。プロパティに対してgetで取得された値を指します。

array.length = -1をトレースするとこうなります。
3.で-1を渡すのでToUint32(-1) // => 42949672955となります。
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

  1. ECMAScript® 2021 Language Specification#sec-object-type

  2. ECMAScript® 2021 Language Specification#sec-array-exotic-objects

  3. プロパティの削除が出来ないなどの意味を持ちます。

  4. ECMAScript® 2021 Language Specification#sec-arraysetlength

  5. ToUint32についてなにをやっているかは、仕様を見ていただくか、JavaScriptのビット演算の仕組みを理解する - 風と宇宙とプログラム こちらのブログが大変わかりやすいです。

270
156
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
270
156

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?