2021/3/10:この記事は別のブログで投稿した記事を移動したものです。
まえがき
JavaScriptの文字列はUTF-16なのでサロゲートペアを含みます(少なくとも2019年12月6日のMDN バイナリー文字列)。例えば文字列"012abcあいう"
では想定通り動作するコードも文字列"0aあ🍊
"に対しては想定外に動作する場合があります。今回はその具体例として文字列にArray.from
関数を適用した場合と解決策の一つを紹介します。
間違ったコードと注意点
文字列にArray.from
関数を適用すると文字の配列が得られます。
Array.from("01aあ🍊") // Array(5) [ "0", "1", "a", "あ", "🍊" ] Array.from("01aあ🍊", s => typeof(s)) Array.from("01aあ🍊", s => s.length) // Array(5) [ "string", "string", "string", "string", "string" ] // Array(5) [ 1, 1, 1, 1, 2 ]
適用結果について次の2点に注意が必要です。
- 戻り値は
string
型の配列であり、length
を持つ。 - 文字
["0"、"1"、"a"、"あ"]
のlength
は各1、文字"🍊"
のlength
は2。"🍊"はサロゲートペア。
この性質により次のコードは想定外の結果を返します。"🍊"(length
は2)がs[0]
により想定外の"\ud83c"
へ変化します。
Array.from("01aあ🍊", s => s[0]) // Array(5) [ "0", "1", "a", "あ", "\ud83c" ] Array.from("01aあ🍊", s => s[0]).join("") "01aあ\ud83c"
正しいコード
文字列中のサロゲートペアに対応するにはs
がs[0]
だけの場合とs[1]
もある場合の両方を考慮します。Array.from
関数の第2引数mapFn
でs.length
の数だけ求める処理を繰り返してからflat
関数でまとめることは一つの解決策です。
Array.from("0aあ🍊", s => [...Array(s.length).keys()].map(i => s[i])).flat() // Array(5) [ "0", "a", "あ", "\ud83c", "\udf4a" ] Array.from("0aあ🍊", s => [...Array(s.length).keys()].map(i => s[i])).flat().join("") // "0aあ🍊" Array.from("0aあ🍊", s => [...Array(s.length).keys()].map(i => s.charCodeAt(i))).flat() // Array(5) [ 48, 97, 12354, 55356, 57162 ]
備考:文字列のlength
とArray.from
関数
文字列のlength
はサロゲートペアを2文字として扱う長さを返します。Array.from
のlength
はサロゲートペアも1要素とする文字列配列を返します。したがって、これらの返す長さはサロゲートペアが含まれる場合に一致しません。
"01aあ🍊".length // 6 Array.from("01aあ🍊").length // 5(サロゲートペアが1文字扱いなのでlengthと異なる)