僕は、なぜ絵文字の長さが、直感に反するのか理解したい...!!

対象者

- UnicodeやUTF-16について、よくわかってない人 -> [ここから](https://qiita.com/youya66/items/e2272b8da466c4e01c47#%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%82%82%E3%81%86%E4%B8%80%E5%BA%A6)
- "😀".split("")で文字化けする理由がわからない人 -> [ここから](https://qiita.com/youya66/items/e2272b8da466c4e01c47#es2015%E3%81%A7%E7%B5%B5%E6%96%87%E5%AD%97%E3%81%AE%E9%95%B7%E3%81%95%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)
- [..."👨‍👩‍👧"].lengthが5になる理由がわからない人 -> [ここから](https://qiita.com/youya66/items/e2272b8da466c4e01c47#%E5%8F%8D%E5%BE%A9%E5%87%A6%E7%90%86%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%ABiteration-protocols)


# 文字コードについてもう一度
文字コードは以下の二つで構成されています

- `符号化文字集合`: 文字と、その文字の位置を示す一意の番号の集合
- `文字符号化方式`: 文字に振られた番号をバイト表現にエンコードする方法

## 符号化文字集合

符号化文字集合は、

- 文字
- その文字の位置を示す一意の番号

この二つの組み合わせの集合のことを指します。

```javascript
"A".codePointAt() 
// 65 ← これは10進数。16進数だと41。
```

例えば`ASCII`では 8bit(128通り) でラテン文字や英数字を表現しています。

しかし`ASCII`には日本語などの非英語圏の文字が収録されていません。
そのため、日本語を収録した`Shift-JIS`やアジア圏の文字を収録した`EUC`といった規格ができました。

ただ、規格が乱立したため、グローバルスタンダードを作ろうということになり
世界中の文字を収録した `Unicode`が誕生しました。

## Unicodeとサロゲートペア
`ASCII`は128個の文字集合なので、8bitで収まりましたが、
`Unicode`では世界中の言語を収録するため、8bit(128通り)では表現できなくなりました。

そこで当初は16bit(65536通り、`U+0000~U+FFFF`) で全ての文字を表現しようと試みました。

しかし、残念なことに世界中の文字は65536通りで収まりませんでした。
(中華字海という中国で出版された漢字辞典には85568字収録されてるみたいです)

そこで16bitの`Unicode`の領域の2048文字のうち

- 前半の `U+D800 ~ U+DBFF`
- 後半の `U+DC00 ~ U+DFFF`

を2つ組み合わせて使うことで、
1024 * 1024 = 1048576通り(`U+010000～U+10FFFF`) に表現できる文字が増えました。
この2つで1つの文字を表すことを、`サロゲートペア` と呼びます。

_Memo: U+XXXXは `Unicodeスカラ値`と呼ばれ、ユニコードのコードポイント(その文字の位置を示す一意の番号)を示します。XXXX の部分は16進数で表現されます_

_Memo: 16bit(U+0000～U+FFFF)で表現できる文字を`基本多言語面(BMP)`、残り(U+010000~U+10FFFF)を`追加多言語面(SMP)`と呼びます_

```javascript
"A".codePointAt() // 10進数 65、スカラ値 U+0041
"あ".codePointAt() // 12354、U+3042
```

## 文字符号化方式
`文字符号化方式` とは
8bitで収まらなくなった文字集合をどのようにエンコードするのかを指定しています。
例えば、

- `UTF-8`: 英数字は8bitで表現して、それ以外は16,24,32bitで可変的に表現する
- `UTF-16`: 基本多言語面で表現できるものもは全て16bitで表現して、
サロゲードペアが必要なら32bitで表現する

のようなものが有名です

では、`あ`という文字をutf-8とutf-16でエンコードして出力してみます

```shell
echo -n あ| iconv -t utf-8 | hexdump -C
e3 81 82

echo -n あ| iconv -t utf-16 | hexdump -C
30 42
```
このように文字符号化方式(エンコード方法)によって、符号化される値が異なることがわかります。

_Memo: 日本語だけなら上記を見ればわかるようにutf-8よりutf-16の方がサイズは大きくなります_

## 誤解を招きやすいASCII
utf-8は`ASCII`と互換性があり、
`U+0000~U+007F`はASCIIに対応しています。

```shell
#utf-8の文字列をasciiにエンコードして出力
echo -n a | iconv -f utf-8 -t ascii | hexdump -C
61

#asciiの文字列をutf-8にエンコードして出力
echo -n a | iconv -f ascii -t utf-8 | hexdump -C
61
```

ASCIIは8bitで表現されており、サロゲートペアもないため、文字符号化方式(エンコード)する必要がありません。

そのため冒頭の
> また文字コードは以下の二つで構成されています

> - `符号化文字集合`: 文字と、その文字の位置を示す一意の番号の集合
> - `文字符号化方式`: 文字に振られた番号をバイト表現にエンコードする方法

後者の文字符号化方式がない、符号化文字集合だけで表現できる文字コードもあるということになります.

## 参考
[Unicodeについて](http://maxy.osdn.jp/help/HTML/for_developers/unicode.html)

# es2015で絵文字の長さを取得する
jsのStringはutf-16で表現されています.
またサロゲートペアを表現する方法は、
- `\uXXXX`: utf-16のコードポイント使用する
- `\u{X}`: utf-32のコードポイントを使用する

などがあります

```javascript
"\uD83D\uDE00" // 😀
"\u{1F600}" // 😀
"\uD83D\uDE00" === "\u{1F600}" // true
```
また、`String.length`はUTF-16のコード単位の数を返しています.
[ここらへん](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-ecmascript-language-types-string-type)

```javascript

"😀".length // 2
```
この絵文字(😀)はコードポイントが `U+1F600` で表されており、サロゲートペアで表現されます.
そのため内部的には16bitが2つで表現されているため、文字列の長さが2になります.

また、splitすると文字化けする原因もサロゲートペアで表現していることに起因します.
`String.prototype.split([separator])` のseparatorに `""(empty string)` を渡すと、
文字列は個々のコード単位に分割されます.
[Note1のところ](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-string.prototype.split)

そのため、サロゲートペアが分割されてしまい、文字化けが生じてしまいます.

```javascript

"😀".split("") // ["�", "�"]
JSON.stringify(["\ud83d","\ude00"]) === JSON.stringify("😀".split("")) // true
```

ではこの文字列の長さを1として取得するにはどうするかというと、
es2015から導入された、

- Spread Operator
- Array.from

を使えばある程度は正しく長さを取得できます

```javascript
const str = "hoge" + "😀"
[...str].length // 5
Array.from(str).length // 5
```

## 絵文字を合成する
しかし、絵文字の長さが想定とことなる挙動をする場合もあるので注意が必要です.

```javascript

// 同じに見える
[..."🕵"].length // 1
[..."🕵️"].length // 2
[..."🕵️‍♂️"].length // 5

// 肌の色が異なる
[..."👋"].length // 1
[..."👋🏻"].length // 2

// すごい多い
[..."👨‍👩‍👧"].length // 5

```
絵文字が1つなのにlengthが異なっています.
実はこれ

```javascript

// 同じに見える
[..."\u{1F575}"].length // 1
[..."\u{1F575}\u{FE0F}"].length // 2
[..."\u{1F575}\u{FE0F}\u{200D}\u{2642}\u{FE0F}"].length //5

// 肌の色が異なるだけ
[..."\u{1F44B}"].length // 1
[..."\u{1F44B}\u{1F3FB}"].length // 2

// すごい多い
[..."\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}"]
```
のように内部ではいくつかの文字を組み合わせて表現されています.

このように絵文字は、修飾したり、絵文字同士を組み合わせたりすることができます.
以下では様々な組み合わせを紹介していきます

## プレゼンテーション(Emoji Presentation)
[特定の絵文字](http://www.unicode.org/Public/emoji/12.0/emoji-variation-sequences.txt)では、
テキスト表現になる `U+FE0E`(text presentation sequence)と
絵文字表現になる `U+FE0F` ((emoji presentation selector)があります

特定の絵文字にプレゼンテーションを付与することで、表現を切り替えることができます

↩︎  `\u{21A9}\u{FE0E}`
↩️  `\u{21A9}\u{FE0F}`

左側がテキスト表現、右側が絵文字表現です
テキスト表現と絵文字表現の一覧は[ここ](https://unicode.org/emoji/charts-12.1/emoji-style.html)で見れます

## 修飾詞(Emoji Modifiers)
修飾詞には`Skin Tone`が入っています
🏻🏿
このSkin Toneを用いて、絵文字の色を変えることができます

```javascript

"\u{270A}" // ✊
"\u{270A}\u{1F3FB}" // ✊🏻
"\u{270A}\u{1F3FF}" // ✊🏿
```
トーン絵文字の一覧は[ここ](https://unicode.org/emoji/charts/full-emoji-modifiers.html)で見れます

## シーケンス(Emoji Sequences)

### zwj (zero with joiner)
`zwj(\u{200d})` は絵文字と絵文字を組み合わせるため使用します.

人間の形をした絵文字に、`zwj`を使って、性別を追加することができます(human-form + sign)

```javascript
"\u{1F646}" // 🙆

"\u{2640}" // ♀
"\u{1F646}\u{200D}\u{2640}\u{FE0F}" // 🙆‍♀️

"\u{2642}" // ♂
"\u{1F646}\u{200D}\u{2642}\u{FE0F}" // 🙆‍♂️
```

human-formの一覧(person-XXXのperson-role以外)
[person](https://unicode.org/emoji/charts/full-emoji-list.html#person)
[person-fantasy](https://unicode.org/emoji/charts/full-emoji-list.html#person-fantasy)
[person-activity](https://unicode.org/emoji/charts/full-emoji-list.html#person-activity)
[person-sport](https://unicode.org/emoji/charts/full-emoji-list.html#person-sport)
[person-resting](https://unicode.org/emoji/charts/full-emoji-list.html#person-resting)


_Memo: 文末に `\u{FE0F}` がなくても、正しく表示されます. 推奨されている挙動は[ここ](https://unicode.org/reports/tr51/#Emoji_Modifiers)を確認してみてください_

また、職業にも性別を追加することができます(gender + object)

```javascript
"\u{1F4BB}" // 💻
"\u{1F468}" // 👨
"\u{1F469}" // 👩
"\u{1F468}\u{200D}\u{1F4BB}" //👨‍💻
"\u{1F469}\u{200D}\u{1F4BB}" // 👩‍💻

// 一応性別不詳も用意されていますが、僕の環境では正しく表示できませんでした
"\u{1F9D1}" //🧑
"\u{1F9D1}\u{200D}\u{1F4BB}" //🧑‍💻
```
roleの一覧
[person-role](https://unicode.org/emoji/charts/full-emoji-list.html#person-role)

さらに、人と人を組みわせて、家族の絵文字を作ることもできます

```javascript
"\u{1F468}" // 👨
"\u{1F469}" // 👩
"\u{1F466}" // 👦
"\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}" //👨‍👩‍👦

"\u{1F467}" // 👧
"\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F467}" //👨‍👩‍👧‍👧
```

zwjを使った絵文字の組み合わせの一覧は[ここ](https://unicode.org/emoji/charts/emoji-zwj-sequences.html)で見れます

### キーキャップ(emoji keycap sequence)
`数字,#,*` の絵文字の後ろに `\u{FE0F}\u{20E3}` を追加すると1つの絵文字になります

```javascript

"\u{0023}\u{FE0F}\u{20E3}" // #️⃣
"\u{0036}\u{FE0F}\u{20E3}" // 6️⃣
```
キーキャップの一覧は[ここ](https://unicode.org/emoji/charts/full-emoji-list.html#keycap)でみれます

### 旗
国旗は、[ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)で定義されているコードを、Regional Indicator Symbol Letter と呼ばれる文字を組み合わせて表現します.

```javascri
"\u{1F1EC}" // 🇬
"\u{1F1E7}" // 🇧
"\u{1F1EC}\u{1F1E7}" //🇬🇧
```
また、イングランドやフィンランドのような下位区分の場合は

`🏴(\u{1F3F4}) + 国コード(GB) + その後に下位区分(ENG) + \u{E007F}`

のように指定します.
また `国コード + 下位部分`はTag Charactersと呼ばれる文字で表現します.

```javascript
"\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}" // 🏴󠁧󠁢󠁥󠁮󠁧󠁿
```

また、zwj を使って、`🏳(U+1F3F3)`と`🌈(U+1F308)`を組み合わた`🏳️‍🌈(Rainbow Flag)`もあります

```javascript
"\u{1F3F3}\u{FE0F}\u{200D}\u{1F308}" //🏳️‍🌈
```

旗の一覧は[ここ](https://unicode.org/emoji/charts/full-emoji-list.html#flags)で見れます

## 参考
qiita -> [絵文字を支える技術の紹介](https://qiita.com/nonanona/items/b148c212ba7c24942e93)


# 反復処理プロトコル(Iteration protocols)
では`[..."😀"].length`は 1 になるのに対して、`[..."👨‍👩‍👧"].length`は 5 になる理由を探ってみたいと思います

es2015 から導入された、`Array.from` や`[...value]`は内部的には`for-of(反復)`の仕組みを使っています.

```javascript
let arr = [];
for (let str of "😀"){
  arr.push(str)
}
// arr -> ["😀"]
JSON.stringify(arr) === JSON.stringify([..."😀"]) // true

let arr2 = [];
for (let str of "👨‍👩‍👧"){
  arr2.push(str);
}
// arr2 ->["👨", "‍", "👩", "‍", "👧"]
JSON.stringify(arr2) === JSON.stringify([..."👨‍👩‍👧"]) // true
```
ここから先は es2015 から導入された、オブジェクトを反復するための `反復処理プロトコル`について文字が関係している部分を解説していきます.

## 反復処理プロトコルとは

- iterable プロトコル
- iterator プロトコル

の二つで構成されています。

## iterable プロトコル
iterable プロトコルにより、`for..of` で反復的な処理をするループを作成、カスタマイズできます.

またjavascript オブジェクトは内部的に `@@iterator`メソッドを持ちます.
`@@iteratorメソッド`は`[Symbol.iterator]`で定義する引数なしの関数です.
また、`String, Array, TypedArray, Map, Set` はすでに定義されています(built-in iterables)

```javascript
const str = "a"
const $$iterator = str[Symbol.iterator] 
// ƒ [Symbol.iterator]() { [native code] }

const $$strIterator = str[Symbol.iterator]()
// StringIterator {}
````

## iterator プロトコル
この `@@iterator関数` は iterator プロトコルに準拠したオブジェクトを返します.
iterator プロトコルは 

- next

という引数なしの関数を返します.
また`next`関数は

- done(boolean): 終了したかどうかのboolean値
- value: 任意の値. done property が trueの場合は省略

を返します.

```javascript
const str = "a"
const $$iterator = str[Symbol.iterator]()
$$iterator.next() //　{value: "a", done: false}
$$iterator.next() //　{value: undefined, done: true}
```

## `String.prototype [@@iterator]()`
最後に`String.prototype [@@iterator]()`]についてです.
先ほどのコードにある

```javascript
const $$strIterator = str[Symbol.iterator]()
// StringIterator {} 
```
`StringIterator` の中に答えがあります.
仕様は[ここ](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-string.prototype-@@iterator)

ここにはStringの`built-in iterables`が実行された時の挙動について書かれています.
今回の絵文字のついては[ここ](%StringIteratorPrototype%.next ( ))にある、`next`を実行した時が関係しています.

>  抜粋
> 10. If first < 0xD800 or first > 0xDBFF or position + 1 = len, let resultString be the String value consisting of the single code unit first.
11. Else

>> a, Let second be the numeric value of the code unit at index position + 1 within the String s.

>> b, If second < 0xDC00 or second > 0xDFFF, let resultString be the String value consisting of the single code unit first.

>> c, Else, let resultString be the string-concatenation of the code unit first and the code unit second.



つまり、0xD800 <= value[position] <= 0xDBFF かつ 0xDC00 <= value[position + 1] <= 0xDFFF
だった場合は文字列連結をするため、サロゲートペアは1文字、zwjやskinはここに含まれないので1文字としてカウントされてまいます.

```javascript
// -> 上記のfor-ofのcount数と同じになります

const str = "😀"
const $iterator = str[Symbol.iterator]();
$iterator.next().value // 😀
$iterator.next().value // undefined

const family = "👨‍👩‍👧"
const $$iterator = family[Symbol.iterator]();
$$iterator.next().value //👨
$$iterator.next().value // "" <- zwj
$$iterator.next().value // 👩
$$iterator.next().value // "" <- zwj
$$iterator.next().value // 👧
$$iterator.next().value // undefined
```

つまり、`Array.from`や`[...value]`でStringが配列に変換される際に`for-of`が内部で実行されるので、
`[..."😀"]` は `["😀"]` になり length が 1 になり
`[..."👨‍👩‍👧"]` は `["👨", "‍", "👩", "‍", "👧"]`になり length が 5 になります

## 参考
MDN -> [反復処理プロトコル](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols#Built-in_iterables)
もっと詳しくiteratorについて知りたい人 -> [https://2ality.com/2015/02/es6-iteration.html](https://2ality.com/2015/02/es6-iteration.html)

# まとめ
文字コードから、絵文字の仕様、そしてes2015のイテレーターまで解説しました.
サロゲートペアだけでなく、zwjや、skinを考えると、
絵文字の長さを直感的(grapheme??)に取得するのは相当めんどくさくなります.

フィードバック や レビュー待ってます:muscle:


おーーわりっ!!