JavaScriptのStringについて part1

# 初めに
今回は文字列に関連するメソッドをまとめてみました。

https://javascript.info/string

# Methods
## String.prototype.charCodeAt()
`charCodeAt()`は指定された文字列のインデックスに、対応したUTF-16の`Code Unit`（`0`-`65535`）を返す。
**サロゲートペアは二つの`Code Unit`の組み合わせなので、`charCodeAt()`は一部の`Code Unit`を返すことしかできません。**
```jsx
// Syntax
str.charCodeAt(index)
// index in here means "code unit index"
// it will return UTF-16 "code unit"
```
```jsx
console.log('abc'.charCodeAt(0)); // 97
console.log('abc'.charCodeAt(1)); // 98

// surrogate pair
console.log('𝒳'.length); // 2
console.log('𝒳'.charCodeAt(0)); // 55349
console.log('𝒳'.charCodeAt(1)); // 56499
```

## String.prototype.codePointAt()
`charCodeAt()`と違い、Unicodeの`Code Point`を返す。（`Code Unit`（`0`-`65535`）は`charCodeAt()`とは変わらない。）
**`Code Point`単位で返すのでサロゲートペアもうまく対応できる。**
```jsx
// Syntax
str.codePointAt(position)
```
```jsx
console.log('abc'.codePointAt(0)); // 97
console.log('abc'.codePointAt(1)); // 98

// surrogate pair
console.log('𝒳'.length); // 2
console.log('𝒳'.codePointAt(0)); // 119987
```

### String.prototype.charCodeAt() vs. String.prototype.codePointAt()
`charCodeAt()`も`codePointAt()`もユニコード`\u`から文字に表示させる前に、まず一度16進数（hex digits）に変換する必要があります。
```jsx
console.log('a'.charCodeAt(0)); // 97
console.log('a'.charCodeAt(0).toString(16)); // 61

console.log('a'.codePointAt(0)); // 97
console.log('a'.codePointAt(0).toString(16)); // 61

console.log('\u0061'); // a
```
四桁なら`\uXXXX`。一から六桁までは`\u{X...XXXXXX}`で文字に変換する。
```jsx
console.log('𝒳'.charCodeAt(0)); // 55349
console.log('𝒳'.charCodeAt(1)); // 56499
console.log('𝒳'.charCodeAt(0).toString(16)); // d835
console.log('𝒳'.charCodeAt(1).toString(16)); // dcb3
console.log('\ud835\udcb3'); // 𝒳

console.log('𝒳'.codePointAt(0)); // 119987
console.log('𝒳'.codePointAt(0).toString(16));
console.log('\u{1d4b3}'); // 𝒳

console.log('\u{61}'); // a
```
`charCodeAt()`と`codePointAt()`と一番の違いは、
`charCodeAt()`はUTF-16の`Code Unit`単位で指定インデックスのコードを返すが、
`codePointAt()`はUnicodeの`Code Point`単位で、2つの`Code Unit`を占めたサロゲートペアもまとめたコードを返す。

https://jsprimer.net/basic/string-unicode/#code-point-is-not-code-unit

## String.fromCharCode()
指定された`Code Unit`から文字に変換する。複数個も可能です。
```jsx
// Syntax
String.fromCharCode(num1[, ..., numN])
```
```jsx
console.log('abc'.charCodeAt(0, 1, 2)); // 97 // only for one index
console.log(String.fromCharCode(97)); // a
console.log(String.fromCharCode(97, 98, 99)); // abc

// decimal
console.log('𝒳'.charCodeAt(0)); // 55349
console.log('𝒳'.charCodeAt(1)); // 56499
console.log(String.fromCharCode(55349, 56499)); // 𝒳

// hexadecimal
console.log('𝒳'.charCodeAt(0).toString(16)); // d835
console.log('𝒳'.charCodeAt(1).toString(16)); // dcb3
console.log(String.fromCharCode(0xd835, 0xdcb3)); // 𝒳
console.log('\ud835\udcb3'); // 𝒳

console.log(String.fromCharCode(128970)); //  // it is over 65535(0xFFFF)
```
上のように`String.fromCharCode()`は複数個の`Code Unit`を使えるので、サロゲートペアへもうまく転換することができる。また、10進数、16進数も受け入れる。
しかしUTF-16`Code Unit`の範囲（`0`(0x0000)-`65535`(0xFFFF)）を超えた正しく表現できません。

## String.fromCodePoint()
`String.fromCodePoint()`は`Code Point`単位で文字を変換する。
```jsx
// Syntax
String.fromCodePoint(num1[, ..., numN])
```
`String.fromCodePoint()`も10進数と16進数を受け入れる。
```jsx
console.log('𝒳'.codePointAt(0)); // 119987
console.log('𝒳'.codePointAt(0).toString(16)); // 1d4b3

console.log(String.fromCodePoint(119987)); // 𝒳 // decimal
console.log(String.fromCodePoint(0x1d4b3)); // 𝒳 // hexadecimal
console.log('\u{1d4b3}'); // 𝒳
```

### String.fromCharCode() vs. String.fromCodePoint()
`String.fromCodePoint()`は`0`(0x0000)-`65535`(0xFFFF)を超えても正しく変換できます。
```jsx
console.log(String.fromCharCode(128970)); // 
console.log(String.fromCharCode(0x1F7CA)); // 

console.log(String.fromCodePoint(128970)); // 🟊
console.log(String.fromCodePoint(0x1F7CA)); // 🟊
```

https://unicode-table.com/jp/1F7CA/


## String.prototype.indexOf()
引数と同じ文字列（×正規表現）が見つかれば`index`を返す。見つからない場合は`-1`を返す。
```jsx
// Syntax
str.indexOf(substr, position)
// position = fromIndex
```
二番目の引数`pos`で検索の開始位置を指定することができる。
```jsx
let str = 'Widget with id';
console.log(str.indexOf('Widget')); // 0 // 'Widget'
console.log(str.indexOf('widget')); // -1
console.log(str.indexOf('id')); // 1 // W'id'get
console.log(str.indexOf('id', 1)); // 1

console.log(str.indexOf('id', 2)); // 12 // 'id'
```
```jsx
let str = 'As sly as a fox, as strong as an ox';
let target = 'as';
let pos = -1;
while ((pos = str.indexOf(target, pos + 1)) !== -1) {
  console.log(pos);
}
// 7
// 17
// 27
```
```jsx
let str = "Widget with id";

if (str.indexOf('Widget')) {
  console.log('We found it');
}
// doesn't work, because indexOf return 0

if (str.indexOf('Widget') !== -1) {
  console.log('We found it')
}
// We found it
```

## String.prototype.lastIndexOf()
`indexOf()`とは逆方向に検索を行う。
```jsx
// Syntax
str.lastIndexOf(substr, position)
```
```jsx
let str = 'As sly as a fox, as strong as an ox';
let target = 'as';
console.log(str.lastIndexOf(target, 20)); // 17
console.log(str.lastIndexOf(target, 10)); // 7
```

## String.prototype.includes()
（×正規表現）
目標の文字列と一致したら`true`、しなかったら`false`。
```jsx
// Syntax
str.includes(substr, position)
```
```jsx
console.log('Widget'.includes('id')); // true
console.log('Widget'.includes('id', 3)); // false
```

## String.prototype.startsWith() & String.prototype.endsWith()
（×正規表現）
目標の文字列の始まりや終わりが指定された文字列と同じ場合は`true`、でなければ`false`。
```jsx
// Syntax
str.startsWith(substr, position)
str.endsWith(substr, position)
```
この二つのメソッドでは`\n`など改行・空白文字も文字列の一部と見なされている。
```jsx
console.log('Widget'.startsWith('Wid')); // true
console.log('\nWidget'.startsWith('Wid')); // false
console.log('Wid\nget'.endsWith('get')); // true
console.log('Widget\n'.endsWith('get')); // false
```
## String.prototype.slice()
指定したインデックスから（元の文字列を変更せず）文字列を切り取る。
```jsx
// Syntax
str.slice(start[, end])
```
`end`が指定されてない場合は最後まで切り取る、
`end`が指定される場合は`end`の文字列を含まれず切り取る。
```jsx
let str = 'stringify';
console.log(str.slice(2)); // ringify
console.log(str.length); // 9
console.log(str.slice(2, 8)); // ringif // 'y' index is 8, the length is 9
console.log(str.slice(2, 9)); // ringify
console.log(str.slice(2, 10)); // ringify
```
負数の指定というのは逆方向からです。最後のインデックスは`-1`と見なされる。
```jsx
console.log(str.slice(-4, -1)); // gif
```

## String.prototype.substring()
指定した区間位置の文字列を返す。
```jsx
// Syntax
str.substring(start [, end])
```
`end`の文字列は含まれない。
```jsx
let str = 'stringify';
console.log(str.substring(2, 6)); // ring
console.log(str.substring(6, 2)); // ring
console.log(str.substring(-4, -8)); // '' // not supported
```
負数の指定は支援されていません。

## String.prototype.localeCompare()
二つの文字列（頭文字）を比べるメソッドです。
```jsx
// Syntax
referenceStr.localeCompare(compareStr, locales, options)
// Result
referenceStr > compareStr => positive
referenceStr < compareStr => negative
referenceStr = compareStr => 0
```
引数が参照文字列のコードポイントより後（大きい）は**正数**、
引数が参照文字列のコードポイントより前（小さい）は**負数**、
両者同じである場合は`0`。
（環境により正数／負数の表現は変わるが、正数／負数の判定は変わりません。）

- `locales`ロケールはIETF言語タグを使います。

https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags

- `options`は`Intl.Collator() constructor`の設定に従います。（デフォルトメソッドは`sort`、`sort`の`sensitivity`は`variant`）

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#locales

（`Intl.Collator() constructor`ではプロパティのより詳細な設定ができる）

```jsx
console.log('a' > 'A');
// true
console.log(`a: ${'a'.codePointAt(0)}, A: ${'A'.codePointAt(0)}`);
// a: 97, A: 65
console.log('a'.localeCompare('A'));
// -1
console.log('a'.localeCompare('A', 'en'));
// -1
console.log('a'.localeCompare('A', 'en', { sensitivity: 'base' }));
// 0
console.log('a'.localeCompare('A', 'en', { sensitivity: 'accent' }));
// 0
console.log('a'.localeCompare('A', 'en', { sensitivity: 'case' }));
// -1
console.log('a'.localeCompare('A', 'en', { sensitivity: 'variant' }));
// -1

/* note:
sensitivity: 'base' => a ≠ b, a = á, a = A
sensitivity: 'accent' => a ≠ b, a ≠ á, a = A
sensitivity: 'case' => a ≠ b, a = á, a ≠ A
sensitivity: 'variant' => a ≠ b, a ≠ á, a ≠ A
The default is "variant" for usage "sort"; it's locale dependent for usage "search".
*/
```
```jsx
console.log(`á: ${'á'.codePointAt(0)}, a: ${'a'.codePointAt(0)}`);
// á: 225, a: 97
console.log('á'.localeCompare('a'));
// -1 // compare with codePoint
console.log('á'.localeCompare('a', 'en', { sensitivity: 'variant' }));
// 1 // compare with IETF tags

console.log(['á', 'a'].sort(new Intl.Collator().compare))
// [ 'á', 'a' ]
console.log(['á', 'a'].sort(new Intl.Collator('en').compare))
// [ 'a', 'á' ]
console.log(['á', 'a', 'A'].sort(new Intl.Collator('en', { caseFirst: 'upper' }).compare))
// [ 'A', 'a', 'á' ]
```
日本語(ja=jpn=ja-JP)ではひらがなとカタカナとの比べは区別つきません。
```jsx
console.log(`あ: ${'あ'.codePointAt(0)}, ア: ${'ア'.codePointAt(0)}`);
// あ: 12354, ア: 12450
console.log('あ'.localeCompare('ア', 'en'));
// -1 // compare with wrong tag
console.log('あ'.localeCompare('ア', 'ja'));
// 0
console.log('あ'.localeCompare('ア', 'jpn'));
// 0
console.log('あ'.localeCompare('ア', 'ja-JP'));
// 0

console.log('あ'.localeCompare('ア', 'ja', { sensitivity: 'base' }));
// 0
console.log('あ'.localeCompare('ア', 'ja', { sensitivity: 'accent' }));
// 0
console.log('あ'.localeCompare('ア', 'ja', { sensitivity: 'case' }));
// 0
console.log('あ'.localeCompare('ア', 'ja', { sensitivity: 'variant' }));
// 0
```
漢字もコードポイントで比べる。
```jsx
console.log(`太: ${'太'.codePointAt(0)}, 次: ${'次'.codePointAt(0)}`);
// 太: 22826, 次: 27425
console.log('太郎'.localeCompare('次郎', 'ja', { sensitivity: 'base' }));
// 1
console.log('太郎'.localeCompare('次郎', 'ja', { sensitivity: 'accent' }));
// 1
console.log('太郎'.localeCompare('次郎', 'ja', { sensitivity: 'case' }));
// 1
console.log('太郎'.localeCompare('次郎', 'ja', { sensitivity: 'variant' }));
// 1
```

## String.prototype.normalize()
文字列を指定した形式で正規化した表現として返す。
```jsx
// Syntax
str.normalize(form)
// note
NFC => normalize with composed canonical form
NFD => normalize with decomposed canonical form
// default is NFC
```
`NFC`は合成正規形、複数の`Code Point`で合成された文字列を取得し、正確に準ずる等価性を与える。
`NFD`は分解正規形、単一の`Code Point`から分解した文字列を取得し、正確に準ずる等価性を与える。
ほかには`NFKC`、`NFKD`という形式があるが、ここでは省けます。

初めて見たメソッドですが、色々と試してみると`normalize()`は特定の組み合わせだけ作用するという感じです。
```jsx
console.log('S\u0307'); // Ṡ // S + dot above
console.log('S\u0307\u0323'); // Ṩ // S + dot above + dot below
console.log('S\u0323\u0307'); // Ṩ // S + dot below + dot above
console.log('\u1e68'); // Ṩ // S with two dots

console.log('S\u0307'.normalize('NFC') === 'S\u0307\u0323'.normalize('NFC')) // false
console.log('S\u0307'.normalize('NFC') === 'S\u0323\u0307'.normalize('NFC')) // false
console.log('S\u0307'.normalize('NFC') === '\u1e68'.normalize('NFC')) // false

console.log('S\u0307\u0323'.normalize('NFC') === '\u1e68'.normalize('NFC')) // true
console.log('S\u0323\u0307'.normalize('NFC') === '\u1e68'.normalize('NFC')) // true
```
カスタマイズではなく、ちゃんと形の合わせた`Code Point`の組み合わせでないと`normalize()`は`false`を返してくる。

最初は分解、合成という説明と例を見たら、これもサロゲートペアのようなものなのかな？って思ったんですが、実際[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/normalize)から取った例とテストの文字列を加えたら、
```jsx
console.log('é'.length); // 1
console.log('é'.charCodeAt(0)); // 233

const latinSmallAcuteE = '\u00e9';
const unNormalizeE = '\u0065\u0301';
// console.log(String.fromCodePoint(0x0065)); // e
const testE1 = 'e\u0307';
const testE2 = 'e\u0303';

console.log(`${latinSmallAcuteE}, ${unNormalizeE}`);
// é, é
console.log(`${testE1}, ${testE2}`);
// ė, ẽ

console.log(latinSmallAcuteE.normalize('NFC') === unNormalizeE.normalize('NFC'));
// true
console.log(latinSmallAcuteE.normalize('NFC') === testE1.normalize('NFC'));
// false
console.log(latinSmallAcuteE.normalize('NFC') === testE2.normalize('NFC'));
// false

console.log(latinSmallAcuteE.normalize('NFC').length, unNormalizeE.normalize('NFC').length);
// 1 1
```
`NFC`は上の例のように、（特定の）合成された組合せがすでに実在している文字に等価である正しさ(`true`)を付与されました。

https://unicode-table.com/jp/00E9/

その逆に`NFD`は分解正規形、すでにある文字が特定の組合せに等価であるようにされる。
```jsx
// console.log('ñ'.length); // 1
// console.log('ñ'.charCodeAt(0)); // 241

const latinSmallTildeN = '\u00f1';
const unNormalizeN = '\u006e\u0303';
// console.log(String.fromCodePoint(0x006e)); // n

console.log(latinSmallTildeN.normalize('NFD') === unNormalizeN.normalize('NFD'));
// true
console.log(latinSmallTildeN.normalize('NFD').length, unNormalizeN.normalize('NFD').length);
// 2 2

console.log(latinSmallTildeN.normalize('NFD').charCodeAt(0).toString(16)); // 6e
console.log(latinSmallTildeN.normalize('NFD').charCodeAt(1).toString(16)); // 303
```
`ñ`元の長さは`1`だったけれど、`NFD`の影響で分解されたパーツが各自の対応する`Code Unit`と等価になり、長さも変えられました。

https://unicode-table.com/jp/00F1/