なぜ絵文字を含む文字を1文字ずつに分けるのにArray.fromだけで十分なのか？

[「Node.js で絵文字を使う時の注意点 - Qiita」にコメントした](http://qiita.com/aoki/items/d5b0b6bcbc19c1cbe8c5#comment-3aff3ca5ad39b665077b)のですが、大雑把すぎて説明になっていない気がするので、詳細に解説したいと思います。


# TL;DR

  - 絵文字のようなサロゲートペアを含む文字列をJavaScriptで扱おうとすると面倒。
  - ↑ `s.length`、`s[0]` とか `s.split('')` とかがおかしくなる。
  - 1文字ずつに分割したいときは `Array.from` が使える。
  - なぜなら、`Array.from` は内部的に `for of` 文のようなことをしていて、文字列に対する `for of` はサロゲートペアを考慮するから。


# 問題点と解決まで

絵文字を含む文字、つまりUTF-16の[サロゲートペア]を含むような文字を一文字ずつに分割したい場合、JavaScriptでは問題が生じます。つまり、どういうことかというと、

```js
const kira = 'キラッ🌠' // 見ため上は4文字

console.log(kira.length) //=> 5 （けれど `length` は5文字ってことになる）
```

というように、サロゲートペアが2文字文としてカウントされてしまい、

```js
kira.split('').forEach(s => console.log(s))
//=> キ
//=> ラ
//=> ッ
//=> ?（変な文字)
//=> ?（変な文字）
```

のように、上手く1文字ずつ分割することもできなくなってしまいます。昔の人々はこれに対処するために[なんかすごい正規表現を生み出した](http://liosk.blog103.fc2.com/blog-entry-162.html)らしいのですが、ES2015環境においてはたった一つのメソッドによって解決されてしまいます。
それが `Array.from` です。このメソッドは `arguments` を配列にしたり、DOM要素のリストを配列にしたりするのによく使われるような気がしますが、サロゲートペアを含む文字列を1文字ずつに分割することにも使うことができるのです。

```js
Array.from(kira).forEach(s => console.log(s))
//=> キ
//=> ラ
//=> ッ
//=> 🌠
```

それでは、なぜ `Array.from` で1文字ずつに分割できるのか、というのがこの記事の主題になります。

[サロゲートペア]: https://ja.wikipedia.org/wiki/Unicode#.E6.8B.A1.E5.BC.B5.E9.A0.98.E5.9F.9F


# `Array.from` とは？

ES2015で `Array` に追加されたメソッドの1つです。（もう一つは `Array.of`）
[MDNによると](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/from)、引数として与えた配列っぽいオブジェクトや反復可能オブジェクトを配列に変換するメソッドのようです。

まず、配列っぽいオブジェクト（array-like）というのは、

  - `length` プロパティがある
  - `obj[0], obj[1]` のようにして各要素にアクセスできる

ようなオブジェクトのことになります。このようなオブジェクトを配列に変換する、というのはこんなコードになります。

```js
function arrayLikeToArray(obj) {
  const length = obj.length
  const array = []
  for (let i = 0; i < length; i++) {
    array.push(obj[i])
  }
  return array
}
// 本当は `Array.of.apply(null, obj)` で十分
```

しかし、`String` の `length` プロパティや `obj[0]` のようなアクセスはサロゲートペアを考慮しないので、`Array.from` の内部で文字列は反復可能オブジェクトとして扱われていると予想できます。

実際に、`Array.from`は反復可能オブジェクトかどうかを試してから、そうでないときに配列のようなオブジェクトとして配列に変換することを試みます。

しかし、反復可能オブジェクトとは一体どのようなオブジェクトなのでしょうか？

これには一言で答えることができます。
`for of` 文でループすることができるようなオブジェクトのことです。


# `for of` 文とは？

こちらはES2015にて追加された構文の一つです。`for in` と似ているのですが、 `for in` はプロパティ名を反復するのに対し `for of` はプロパティの値を反復すると[MDNには書かれています](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for...of)。

なので、`for of` を使って配列を作るにはこのようにすればいいです。

```js
function iterableToArray(obj) {
  const array = []
  for (let v of obj) {
    array.push(v)
  }
  return array
}
// 本当は `[...obj]` で十分
```

`for of`、というかES2015のイテレーターについては他に[優秀な](http://tech.chick307.com/2014/05/09/es6-iterator/)[説明記事](http://qiita.com/kura07/items/cf168a7ea20e8c2554c6)がいくつもあるので説明を譲るとして、ここでは `String` を `for of` 文でループさせたときの挙動について説明します。

ここまでの流れをくめば想像しやすいことですが、文字列を `for of` に渡した場合、サロゲートペアも考慮して1文字ずつ反復していきます。

```js
for (let c of kira) {
  console.log(c)
}
//=> キ
//=> ラ
//=> ッ
//=> 🌠
```

なので、`Array.from` でサロゲートペアを考慮して文字列を1文字ずつに分割することができるわけです。


# あとがき

書いている途中で気付いたのですが、`Array.from`じゃなくてspread operatorを使って、`[...kira]` のように書いた方が短かいです。

また、サロゲートペアを含めた1文字だけを取得したいときは `String.prototype.codePointAt` が、 絵文字などをUCS-2のコードポイントから構築したいときは `String.fromCodePoint` が使えます。→[参考](http://qiita.com/YusukeHirao/items/2f0fb8d5bbb981101be0)

それと、サロゲートペアを考慮する、というのは要するにUTF-16文字列をUCS-2のコードポイント毎で考えているということです。Unicodeの結合文字やIVSを考慮してくれるわけではありません。

この文章で自分が言いたかったことは「`Array.from` を単に、配列っぽいオブジェクトを配列にすると考えても反復可能オブジェクトを配列にする、と考えても問題だ」っていうことで、実際のところタイトルにあるような内容は別にどうでもよかったりします。
ES2015っぽいコードで `Array.prototype.slice.call(arguments)` とかを見るとげんなりするので、世界平和のためにも `Array.from` とかspread operatorを使ってやってください。

こんな長い記事に最後までお付き合いいただきありがとうございました。