【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はただでさえ空値のバリエーションが無駄に豊富なので使いすぎると混乱の元かも。容量、用法を守ってお使いください。以上!