JavaScriptである程度のコードを書いたことがある人なら、配列を.sort()
メソッドでソートしたことがある人も多いかと思います。ただ、調べれば調べるほど、複雑な事情が生じてきます。
まずは導入として、以下のコードはどのような配列を生成するか、予想してみてください。
[, null, undefined, 'jkr_2255', 'qiita'].sort()
これがピタリと正解できた場合には、この記事を読む必要はないかもしれません。なお、この文書はECMA-262 第6版(いわゆる「ES6」)を前提としています。数字だけのリンクは、仕様書の各セクションにつながっています。
ソート関数を指定しない場合
まずは、ソート関数を指定しない場合の挙動について考えていきます。この場合、「中身を文字列化して順に並べ替える」ということはよく知られているかと思います(数値が順にならなかった経験をしたことがある人もいるかと思います)。この中身を掘り下げてみます。
ToString
内部操作
よく、値を文字列化するのに、String(value)
やvalue + ''
のような手法が使われますが、標準で行われるソートの比較時に使われるのは、内部名がToString
となっている操作です(22.1.3.24.1)。
このToString
は、以下のような値を返します(7.1.12)。なお、説明の都合上、一部の型のみ書いています。
-
null
→"null"
- ブール値→
"true"
または"false"
- シンボル→
TypeError
を起こす
というわけで、シンボルの入った配列を比較関数なしで.sort()
しようとすると、TypeError
になってしまいます。また、null
は文字列の"null"
と同様の位置に並ぶこととなります。
ちなみに、value + ''
は左辺についてToPrimitive
、それからToString
が行われる一方(12.7.3.1)、String(value)
はシンボルだけ特殊なハンドリングが入っています(21.1.1)。標準関数でシンプルにToString
と同じ結果を得ようとすれば、''.concat(value)
という方法があります。
※ ここについては、@think49 さんからの指摘を元に修正しました。
とはいえ、このあたりはソート関数を適宜定義すればどうにかなる問題です。
ソート関数を指定しても起きる問題…undefined
、値なし
上のToString
のリストからundefined
を省略しましたが、undefined
は比較関数で評価されません。どのような比較関数を使って.sort()
を動かそうとも、undefined
は最後に来ることとなります(22.1.3.24.1)。
そして、値のないプロパティ(Object.prototype.hasOwnProperty.call(arr, index) === false
となるもの)があった場合、undefined
よりさらにあとに来ることとなります。この挙動も、変更できません(ふつうにコードを書いていて、「配列の途中のインデックスが抜けたままになっている」ものに遭遇する機会も、あまり多くはないかとは思いますが)。
実装依存の挙動
Array.prototype.sort
は配列以外にも使える汎用的なメソッドとなっていますが、以下のような場合はソート順が実装依存(≒どうなるかわからない)となります。便宜上、以下の文ではソート対象となるオブジェクトを「配列」としています。
- 渡した比較関数が一貫しない(反射律・対称律・推移律を満たさない、あるいは配列自身あるいはそのプロトタイプを書き換える)場合
- 疎な配列(一部の値がない配列)について、
Object.preventExtensions
がかかっている、あるいはソート範囲内のプロパティで、configurable: false
となっているものがある場合 -
Proxy
オブジェクトなど、内部挙動が特殊なオブジェクトをthis
として.sort()
を実行した場合 - ソート範囲内のプロパティに
writable: false
となったプロパティ、あるいはアクセサプロパティがある場合 - デフォルトの比較関数を使っていて、
-
ToString
の結果が一貫しないオブジェクトが配列に入っている場合 -
ToString
を実行すると、配列自身あるいはそのプロトタイプを書き換えるようなオブジェクトが配列に入っている場合
-
最初の答え
["jkr_2255", null, "qiita", undefined, /* 空き */]