JavaScriptに関するお知らせ

SINCE2019
>
【JS】nullでもundefinedでもない空配列(empty)の特徴と便利な使い方

【JS】nullでもundefinedでもない空配列(empty)の特徴と便利な使い方


こんにちは!nullとundefinedは分ける派のMizutani(@sirycity)です!

今日はJSにある空配列(empty)についての話です。JSはただでさえ空値が2つあるのに(nullとundefined)めんどくさいね!

まずはnullとundefinedの違いについて

nullとundefinedの違いについては先週の記事をどうぞ。3行で説明すると、

  • 該当するものがないことを明示するのがnull
  • 該当するものがないことを警告するのがundefined

みたいな感じです。ただしコード規約によってはこの限りではありません!

空配列(empty)とは

概要

空配列(empty)とはnullでもundefinedでもない空値です。emptyは正式な用語ではないですが、chromeでemptyと表示されるしMDNの記事にもemptyって書いてあるのでemptyって言います。

emptyはこれ。

;[]
Array(0)
Array(10)
Array(42)

空配列[]とか長さ0の配列がemptyになるのは分かりやすいけど、初期化してすぐの配列は長さがいくつだろうとemptyです。この辺わかりにくいね。

※厳密にはArray(n)で出るのは配列ではなくイテレータです。MapオブジェクトやhtmlCollectionとかと同じ扱い。

nullとundefinedとemptyの違い

emptyは参照すると全部undefinedになります。これが最初のポイント。長さ1の配列を作って0番目を参照してみるとわかりやすいかも。

[null][0]
// null
// これは本当にnull

[undefined][0]
// undefined
// これも本当にundefined

[][0]
// undefined
// これは本当はempty

Array(0)[0]
// undefined
// これも本当はempty

undefinedとemptyは配列の中か外かを判別できないです。nullは判別できる。nullの存在意義の一つですね。

[null][0]
// null
[null][100]
// undefined

[undefined][0]
[undefined][100]
// どっちもundefined

[][0]
[][100]
// どっちもundefined

Array(0)[0]
Array(0)[100]
// どっちもundefined

あとemptyは厳密には配列じゃないので、.map()とか.reduce()とかができないです。こんな感じ。

[null].map(v => console.log(v))
// これは出力される

[undefined].map(v => console.log(v))
// これも出力される

[].map(v => console.log(v))
// これは出力されない

Array(0).map(v => console.log(v))
// これも出力されない

emptyの短所

具体例

長さが動的な配列を初期化してすぐに.map()したい時は注意。以下は動かない例。

const n = 5

Array(n).map((v, i) => console.log(i + '番目の出力です'))

本当なら「0番目の出力です」「1番目の出力です」...「4番目の出力です」って出力したいけどできません。なぜならemptyだから。だったら初期化にArray()を使わなければいい話なんですが、初期化する時長さを動的に決定したい場合はArray()使うしかないんですね。上の例だとn=10とかに後から変更になるかもしれんしな。

解決策

スプレッド構文で一旦バラしてからもう一度囲めばおk。

;[...Array(n)].map((v, i) => console.log(i + '番目の出力です'))

もしくは.fill()を使ってempty以外で埋めればおk。.fill()は引数なしだとundefinedで埋まります。

Array(n)
  .fill()
  .map((v, i) => console.log(i + '番目の出力です'))

emptyの長所

emptyだけにできることが一つだけあります。それは無を追加すること。って言われても意味分からんな。例を見てみよう。

「無を追加」について

これは一郎がレギュラーで二郎と三郎がレギュラーじゃない例です。

const ichiro = '一郎'
const jiro = '二郎'
const saburo = '三郎'

const regulars = [ichiro]

超簡単ですね。では次に二郎もレギュラーにしてみます。

const ichiro = '一郎'
const jiro = '二郎'
const saburo = '三郎'

const regulars = [ichiro, jiro]

これも簡単ね。じゃあ次に二郎の調子が良ければ二郎、悪ければ三郎ってパターンを見ます。一郎は不動のレギュラーね。

const conditionJiro = true // or false

const regulars = [ichiro, conditionJiro ? jiro : saburo]

まあこれも三項演算子使えば簡単ね。じゃあ二郎の調子が良ければ二郎、悪ければ誰も出ないってパターンなら?

const regulars = conditionJiro ? [ichiro, jiro] : [ichiro]

ん?ちょっと書き方が変わりましたね。そう、誰も出ない、つまり配列に無を追加ってのはJavaScriptではできないんです。こんな風にやればできそうだけど...

const regulars = [ichiro, conditionJiro ? jiro : null]

これだと二郎の調子が悪い時にnull選手が出場しちゃう。「何も追加しない」じゃなくて「nullを追加する」になっちゃうんですね。

といっても、今回は選手が3人だけだったので上みたいな書き方をすれば済む話です。でももし、一郎が100人くらいいたらどうでしょう?

const regulars = conditionJiro
  ? [ichiro, ichiro, ichiro, 省略, jiro]
  : [ichiro, ichiro, ichiro, 省略]

100人の一郎を2回記述しなくてはならず、壮大な二度手間です。この上もし二郎みたいに出たり出なかったりする気分屋さんが10人くらいいたらもう目も当てられません。

せめて「無を追加」みたいな処理ができればいいんですが...

emptyを使った「無を追加」

その「無を追加」、emptyならできるんです。結論から言うとこう。

const regulars = [ichiro, ...(conditionJiro ? [jiro] : [])]

以下解説。

空配列のempty([])はスプレッド構文(...)でバラすと無になります。その状態を配列に追加しようとすると「無に追加」が起こるわけです。上の例だと

  • trueならjiroを一旦配列にした後すぐにバラしたやつ(つまりjiroのまま)を追加
  • falseなら空配列のemptyをバラしたやつ(無)を追加

みたいな処理になる。普通の三項演算子と比較して分かりやすく書くとこんな感じ。

条件 ? trueのときに追加するやつ : falseのときに追加するやつ
... (条件
  ? [trueのときに追加するやつ]
  : []
)

用途

この「無を追加」の使い所は主に.reduce()です。一つ前の記事でも使ってるのでよかったら見てね!

さいごに

「無を追加」はめっちゃ便利。でも、JavaScriptはただでさえ空値のバリエーションが無駄に豊富なので使いすぎると混乱の元かも。容量、用法を守ってお使いください。以上!



PREV
2020-07-02
【JS】flatMapの使い方とreduceやfilterでの代用について

NEXT
2020-07-13
【JS】Reactのstyleの使い方、書き方いろいろ