JavaScriptのStringについて part3

# 初めに
今回はテンプレートリテラルの使い方をまとめていきたいと思います。

# Template literals(Template strings)
## Basic usage

https://es6.ruanyifeng.com/?search=%E9%81%8D%E5%8E%86&x=7&y=8#docs/string#%E6%A8%A1%E6%9D%BF%E5%AD%97%E7%AC%A6%E4%B8%B2

すでに使い慣れたと思ったけど、実際調べたら別の使い方や応用もあるので色々とメモしたいです。
`HTML`のタグとタグの間空白があっても画面に表示されないのであまり気にしませんでしたが、意識しないうちにそれがバグの原因にあったかもしれないので、`trim()`で空白を排除するのがいいと思います。
```jsx
let str = `
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`.trim()
console.log(str)
/*
<ul>
  <li>first</li>
  <li>second</li>
</ul>
*/
```

テンプレートリテラルにはどんな値が入れられても最終的に文字列になる。
```jsx
let obj = {
  x: 1,
  y: 2
};
let re = {
  x: `${obj.x}`,
  y: `${obj.y}`
}
console.log(re)
// { x: '1', y: '2' } // value is string
```

テンプレートリテラルの中でもテンプレートリテラルを用いられる。（入れ子関数みたいなものです。）
```jsx
let userName = [
  { first: 'Taro', last: 'Yamada' },
  { first: 'Jiro', last: 'Yamada' }
];
let temp = (address) => `
<table>
  ${address.map((x) => `
  <tr><td>${x.first}</td></tr>
  <tr><td>${x.last}</td></tr>
  `).join('').trim()}
</table>
`.trim();
console.log(temp(userName))
/*
<table>
  <tr><td>Taro</td></tr>
  <tr><td>Yamada</td></tr>

  <tr><td>Jiro</td></tr>
  <tr><td>Yamada</td></tr>
</table>
*/
```

下のように、暗黙の`''`よりテンプレートリテラルの方が自由に微調整できるし安全です。例えば`API`から貰った`URL`の行頭や末尾に`/`が含んだら組み立て時、毎回`splice()`などのメソッドで正しい文字列を抽出しなければならない。また、結合演算子`+`がもたらした型変換の問題もなくなります。（JSONファイルから取り出しや宣言で型を固定しているなら問題にならないが。）

https://jsprimer.net/basic/string/#built

```jsx
const base = 'http://example.com/resouces/';
const path = '/example.js';

function getResource(baseURL, pathName) {
  return `${baseURL.replace(/\/$/, '')}${pathName}`
}
console.log(getResource(base, path));
// http://example.com/resouces/example.js
```

## Tagged template(Tagged function)
タグつきテンプレート関数です。まだ経験の浅い自分に初めてみた使い方ですが、今回も頭を抱えながら自分なりにまとめてみました。

https://jsprimer.net/basic/string/#tagged-template-function

https://es6.ruanyifeng.com/?search=%E9%81%8D%E5%8E%86&x=7&y=8#docs/string#%E6%A0%87%E7%AD%BE%E6%A8%A1%E6%9D%BF

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals#%E3%82%BF%E3%82%B0%E4%BB%98%E3%81%8D%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/raw

タグ付きテンプレートの内部はどうやって実装しているかは初めてみたときさっぱりでした。いくつの参考文章を読み書きながら大まかにまとめると、
- タグ付きテンプレートは普通の文字列の部分とパラメータ（引数）を分けている。
- 実装のイメージは`reduce()`のように最終的に一つの文字列に結合する。（`for`ループでも実現できる。）

まずは普通のテンプレートとタグ付きテンプレートの違いから比べてみたいと思います。一般の使い方では関数の引数としてテンプレート入れて出力させたら、
```jsx
function fullName(str) {
  console.log(str)
}
fullName(`First name is ${'Taro'}, last name is ${'Yamada'}`);
// First name is Taro, last name is Yamada
```
タグ付きテンプレートなら
```jsx
function fullName(first, ...last) {
  console.log(arguments);
  console.log(first);
  console.log(last);
}
fullName`First name is ${'Taro'}, last name is ${'Yamada'}.`
/*
[Arguments] {
  '0': [ 'First name is ', ', last name is ', '.' ],
  '1': 'Taro',
  '2': 'Yamada'
}
*/
// ['First name is ', ', last name is ', '.']
// ['Taro', 'Yamada']

// note: /First name is /${'Taro'}/, last name is /${'Yamada'}/./
```
上のように、タグ付きテンプレートは`${}`が区切りになって、普通の文字列と`${}`の引数が分けられている。
ではどのように結合して出力するのでしょうか。

```jsx
// reduce((previousValue, currentValue, currentIndex))
function stringRaw(strings, ...values) {
  console.log(arguments)
  return strings.reduce((first, str, i) => {
    console.log([first, values[i - 1], str]);
    return first + values[i - 1] + str;
  });
}
// console.log(stringRaw`First name is ${'Taro'}, last name is ${'Yamada'}.`)
// [Arguments] {
//   '0': [ 'First name is ', ', last name is ', '.' ],
//   '1': 'Taro',
//   '2': 'Yamada'
// }
// ['First name is ', 'Taro', ', last name is ']
// ['First name is Taro, last name is ', 'Yamada', '.']
// First name is Taro, last name is Yamada.
```
タグ付きテンプレートは文字列と引数を分けて配列にするから、`[Arguments]`のように、`strings`は普通の文字列の配列、`...values`は残りの引数を展開する。（引数の個数が固定している場合は`value1`, `value2`などでパラメータ設置してもいいです。）

そして`reduce()`の特性を用いて、`(first, str, i)`という設置で、
一回目の`reduce()`は`first`（指定しなければstrings[0]がデフォルト）、引数の配列[0]と、strings[1]が結合し、
`/First name is /${'Taro'}/, last name is /`、となる。これは二回目の`reduce()`の`first`になります。
それで二回目は`first`、引数の配列[1]、strings[2]が結合、
`/First name is ${'Taro'}, last name is /${'Yamada'}/./`
最終的に、`First name is Taro, last name is Yamada.`となりました。

`for`ループでもタグ付きテンプレートの実装ができます。
```jsx
// First name is Taro, last name is Yamada.
let raw = function (strings, ...values) {
  let output = '';
  let i;
  for (i = 0; i < values.length; i++) {
    output += strings[i] + values[i];
    console.log(output)
  }

  output += strings[i]
  return output;
}
console.log(raw`First name is ${'Taro'}, last name is ${'Yamada'}.`)
// First name is Taro
// First name is Taro, last name is Yamada
// First name is Taro, last name is Yamada.
```
`reduce()`で結合の原理が分かれば`for`ループでも簡単に実現できます。ただ`for`は`reduce()`と少し違って、`strings`の最後の要素をループ外で`output`と結合しなければならない。これはタグ付きテンプレートが文字列への切り取り方と関わっている。タグ付きテンプレートの最後が`${}`じゃなく単なる空白`''`でも切り取るのです。不思議に思ったが、最初は手抜きして`.`を書いてなかったんでこれに気づきました。つまり`for`ループは`strings`最後の要素をカバーできないのです。（`<= values.length`にしてカバーしたら今度は`values[]`が`undefined`になる。）

そしてタグ付きテンプレートではなく、普通の関数の入れ方に戻すと、
```jsx
console.log(raw(['First name is ', ', last name is ', '.'], 'Taro', 'Yamada'))
// First name is Taro
// First name is Taro, last name is Yamada
// First name is Taro, last name is Yamada.
```

もし複雑な処理をする必要ないなら` String.raw`` `でも簡単にできる。
```jsx
console.log(String.raw`First name is ${'Taro'}, last name is ${'Yamada'}.`)
// First name is Taro, last name is Yamada.
```
` String.raw`` `は`\n`、`\u`などのエスケープを無効化して生の文字列に形成してくれるメソッドです。もともとエスケープできず問題を起こすが今は修正されたそうです。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/raw

構造と使い方が分かったら、タグ付きテンプレートはメリットの多いメソッドです。例えばユーザ（`input`から取り出したテキストとか）からの悪意のあるコードもろ過しすることができます。
```jsx
function saferHTML(strings, ...values) {
  let result = ''
  let i
  for (i = 0; i < values.length; i++) {
    let value = String.raw`${values[i]}`
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;");
    result += strings[i] + value
  }
  return (result + strings[i]);
}

let sender = '<script>alert("abc")</script>'; // malicious code
let message = saferHTML`<p>${sender} has sent you a message.</p>`;
console.log(message)
// <p>&lt;script&gt;alert("abc")&lt;/script&gt; has sent you a message.</p>
```

## encodeURIComponent
`エスケープされないもの：A-Z a-z 0-9 - _ . ! ~ * ' ( )`

`encodeURIComponent()`は`URI`（Uniform Resource Identifier）構成要素として特定の文字を`UTF-8`エンコーディングのようにエスケープシーケンスに転換するメソッドです。例えば`URL`には特定の符号文字`? = / &`、または漢字など`UTF-8`にエンコードしないと、訪問先のパスとして識別できないということです。

さきのように` String.raw`` `で`URL`の処理をしたら、
```jsx
const input = 'A&B';
const escapedURL = String.raw`https://example.com/search?q=${input}&sort=desc`;
console.log(escapedURL)
// https://example.com/search?q=A&B&sort=desc

console.log(encodeURIComponent(input))
// A%26B
```
` String.raw`` `はエスケープ無しで生の文字列として処理してくれるだけです。

タグ付きテンプレート関数を使えばユーザからもらった`input`テキストを関数内部で処理する。
```jsx
function escapeURL(strings, ...values) {
  return strings.reduce((result, str, i) => {
    let value = String.raw`${values[i - 1]}`
    return result + encodeURIComponent(value) + str;
  });
}
const input = 'A&B';
const escapedURL = escapeURL`https://example.com/search?q=${input}&sort=desc`;
console.log(escapedURL)
// https://example.com/search?q=A%26B&sort=desc
```
`for`ループで書き換えてもいいです。
```jsx
function escapeURL(strings, ...values) {
  let output = '';
  let i;
  for (i = 0; i < values.length; i++) {
    let value = String.raw`${values[i]}`
    output += strings[i] + encodeURIComponent(value);
  }
  output += strings[i]
  return output;
}
const input = 'A&B';
const escapedURL = escapeURL`https://example.com/search?q=${input}&sort=desc`;
console.log(escapedURL)
// https://example.com/search?q=A%26B&sort=desc
```
HTMLは`UTF-8`でエンコードしているため、`encodeURIComponent()`の応用は`URL`のエスケープのほかにもたくさんあります。

自分はこれまでの勉強ではサロゲート、サロゲートペアなどという文字列の表示にまつわる問題と処理も少し理解しつつ実践してきましたが、今回テンプレートの勉強では`UTF-8`と`UTF-16`の認識はまだまだ足りないと感じて今後の課題にしておきたいと思います。