文字列展開

この章では、ES6 における文字列の変換と拡張について紹介し、次の章では文字列オブジェクトの新しいメソッドを紹介します。

文字の Unicode 表現

ES6 は Unicode のサポートを強化し、文字を表すために \uxxxx を使用できるようにします。ここで、xxxx は文字の Unicode コード ポイントを表します。

「\u0061」
//「あ」

ただし、この表現は \u0000~\uFFFF の間のコード ポイントを持つ文字に限定されます。この範囲外の文字は 2 つの 2 バイトで表す必要があります。

「\uD842\uDFB7」
// "𠮷"

「\u20BB7」
//「7」

上記のコードは、「\u」の直後に「0xFFFF」を超える値(「\u20BB7」など)が続く場合、JavaScript はそれを「\u20BB+7」として認識することを示しています。 「\u20BB」は印刷不可能な文字であるため、スペースのみが表示され、その後に「7」が続きます。

ES6 ではこれが改善されており、コード ポイントが中括弧内に配置されている限り、文字は正しく解釈されます。

「\u{20BB7}」
// "𠮷"

「\u{41}\u{42}\u{43}」
// 「ABC」

こんにちは = 123;
地獄\u{6F} // 123

'\u{1F680}' === '\uD83D\uDE80'
// 真実

上記のコードの最後の例は、中括弧表記が 4 バイトの UTF-16 エンコーディングと同等であることを示しています。

この表現を使用して、JavaScript には文字を表現する 6 つの方法があります。

'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

文字列トラバーサー インターフェイス

ES6 では、文字列のイテレータ インターフェイスが追加されています (詳細については「イテレータ」の章を参照)。これにより、文字列を for...of ループで走査できるようになります。

for (「foo」の codePoint を許可) {
  console.log(コードポイント)
}
// "f"
//「お」
//「お」

文字列のトラバースに加えて、このトラバーサーの最大の利点は、「0xFFFF」より大きいコード ポイントを識別できることです。従来の for ループではそのようなコード ポイントを認識できません。

let text = String.fromCodePoint(0x20BB7);

for (let i = 0; i < text.length; i++) {
  console.log(テキスト[i]);
}
// " "
// " "

for (let i のテキスト) {
  コンソール.ログ(i);
}
// "𠮷"

上記のコードでは、文字列 text には 1 文字しかありませんが、for ループはそれに 2 つの文字 (どちらも印刷不可) が含まれていると判断し、for...of ループはこの文字を正しく識別します。

U+2028 と U+2029 を直接入力します

JavaScript 文字列では、文字の直接入力だけでなく、入力文字のエスケープ形式も許可されます。たとえば、「中」の Unicode コード ポイントは U+4e2d です。この漢字を文字列に直接入力することも、エスケープ形式の \u4e2d を入力することもできます。この 2 つは同等です。

'中' === '\u4e2d' // true

ただし、JavaScript では 5 文字までと規定されており、文字列内で直接使用することはできず、エスケープ形式のみが使用可能です。

  • U+005C: 逆立体線
  • U+000D: キャリッジリターン
  • U+2028: 行区切り文字
  • U+2029: 段落区切り文字
  • U+000A:改行(ラインフィード)

たとえば、バックスラッシュを文字列に直接含めることはできないため、エスケープして \\ または \u005c として記述する必要があります。

このルール自体には問題はありません。問題は、JSON 形式では U+2028 (行区切り文字) と U+2029 (段落区切り文字) を文字列で直接使用できることです。このように、サーバーが出力したJSONを「JSON.parse」で解析すると、直接エラーが報告される場合があります。

const json = '"\u2028"';
JSON.parse(json); // エラーが報告される場合があります。

JSON 形式は凍結されており (RFC 7159)、変更できません。このエラーを解消するために、ES2019 では、U+2028 (行区切り文字) および U+2029 (段落区切り文字) を使用して JavaScript 文字列を直接入力できるようにしています。

const PS = eval("'\u2029'");

この提案によれば、上記のコードはエラーを報告しません。

テンプレート文字列では、これら 2 つの文字を直接入力できるようになったことに注意してください。さらに、正規表現ではこれら 2 つの文字を直接入力できませんが、JSON では正規表現を直接含めることができないため、これは問題ありません。

JSON.stringify() の変換

標準に従って、JSON データは UTF-8 でエンコードされる必要があります。ただし、現在の JSON.stringify() メソッドは、UTF-8 標準に準拠していない文字列を返す可能性があります。

具体的には、UTF-8 標準では、「0xD800」と「0xDFFF」の間のコード ポイントを単独で使用することはできず、ペアで使用する必要があると規定しています。たとえば、\uD834\uDF06 は 2 つのコード ポイントですが、文字 𝌆 を表すにはこれらをペアにする必要があります。これは、「0xFFFF」より大きいコード ポイントを持つ文字を表すための回避策です。 \uDF06\uD834 には対応する文字がないため、2 つのコード ポイント \uD834\uDF06 を単独で使用したり、逆の順序で使用したりすることは違法です。

JSON.stringify() の問題は、0xD8000xDFFF の間の単一のコード ポイントを返す可能性があることです。

JSON.stringify('\u{D834}') // "\u{D834}"

正当な UTF-8 文字が返されることを保証するために、ES2019JSON.stringify() の動作を変更しました。 0xD8000xDFFF の間の単一のコード ポイント、または存在しないペアが見つかった場合は、エスケープされた文字列を返し、次に何を行うかをアプリケーションに任せます。

JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""

テンプレート文字列

従来の JavaScript 言語では、出力テンプレートは通常次のように記述されます (以下では jQuery メソッドが使用されます)。

$('#result').append(
  '<b>' + Basket.count + '</b> ' + があります
  'バスケット内の商品、' +
  '<em>' + Basket.onSale +
  』 が</em>セール中です!
);

上記の書き方は非常に面倒で不便ですが、ES6ではこの問題を解決するためにテンプレート文字列を導入しています。

$('#result').append(`
  <b>${basket.count}</b> 個のアイテムがあります
   バスケットに <em>${basket.onSale}</em> を入れてください。
  販売中です!
`);

テンプレート文字列は文字列の拡張バージョンであり、バックティック (`) でマークされています。通常の文字列として使用することも、複数行の文字列を定義したり、文字列に変数を埋め込んだりするために使用することもできます。

// 通常の文字列
`JavaScript では '\n' は改行です。

//複数行の文字列
`JavaScriptではこれは
 合法ではありません。

console.log(`文字列テキスト行1
文字列テキスト行 2`);

// 文字列に変数を埋め込む
名前 = "ボブ"、時間 = "今日" とします。
「こんにちは ${name}、${time} は元気ですか?」

上記のコードのテンプレート文字列はすべてバッククォートで表されます。テンプレート文字列にバッククォートが必要な場合は、バックスラッシュでエスケープする必要があります。

挨拶 = `\`Yo\`世界!`;

テンプレート文字列を使用して複数行の文字列を表す場合、すべての空白とインデントが出力に保持されます。

$('#list').html(`
<ul>
  <li>最初</li>
  <li>2 番目</li>
</ul>
`);

上記のコードでは、テンプレート文字列内のすべてのスペースと改行が保持されます。たとえば、<ul> タグの前に改行があります。この改行が不要な場合は、trim メソッドを使用して削除できます。

$('#list').html(`
<ul>
  <li>最初</li>
  <li>2 番目</li>
</ul>
`.trim());

テンプレート文字列に変数を埋め込むには、${}内に変数名を記述する必要があります。

関数 authorize(ユーザー, アクション) {
  if (!user.hasPrivilege(action)) {
    新しいエラーをスロー(
      //伝統的な書き方は
      // 'ユーザー'
      // + ユーザー名
      // + ' は ' を行う権限がありません
      // + アクション
      // + '.'
      `ユーザー ${user.name} には ${action} を実行する権限がありません。`);
  }
}

任意の JavaScript 式を中括弧内に配置でき、操作を実行したり、オブジェクト プロパティを参照したりできます。

x = 1 とします。
y = 2 とします。

`${x} + ${y} = ${x + y}`
// 「1 + 2 = 3」

`${x} + ${y * 2} = ${x + y * 2}`
// 「1 + 4 = 5」

obj = {x: 1, y: 2} とします。
`${obj.x + obj.y}`
//「3」

関数はテンプレート文字列から呼び出すこともできます。

関数 fn() {
  「Hello World」を返します。
}

`foo ${fn()} バー`
// foo Hello World バー

中括弧内の値が文字列でない場合は、通常の規則に従って文字列に変換されます。たとえば、中括弧がオブジェクトの場合、デフォルトでオブジェクトの toString メソッドが呼び出されます。

テンプレート文字列内の変数が宣言されていない場合は、エラーが報告されます。

//変数placeは宣言されていません
let msg = `こんにちは、${place}`;
// エラーを報告する

JavaScriptコードはテンプレート文字列の中括弧内で実行されるため、中括弧内に文字列があった場合はそのまま出力されます。

「こんにちは ${'ワールド'}」
// "こんにちは世界"

テンプレート文字列はネストすることもできます。

const tmpl = addrs => `
  <テーブル>
  ${addrs.map(addr => `
    <tr><td>${addr.first}</td></tr>
    <tr><td>${addr.last}</td></tr>
  `).join('')}
  </テーブル>
`;

上記コードでは、テンプレート文字列の変数に別のテンプレート文字列を埋め込んでいます。使用方法は次のとおりです。

定数データ = [
    { 最初: '<ジェーン>'、最後: 'ボンド' },
    { 最初: 'ラース'、最後: '<クロフト>' },
];

console.log(tmpl(データ));
// <テーブル>
//
// <tr><td><ジェーン></td></tr>
// <tr><td>ボンド</td></tr>
//
// <tr><td>ラース</td></tr>
// <tr><td><クロフト></td></tr>
//
// </テーブル>

テンプレート文字列自体を参照し、必要に応じて実行する必要がある場合は、関数として記述することができます。

let func = (name) => `こんにちは ${name}!`;
func('ジャック') // 「こんにちは、ジャック!」

上記のコードでは、テンプレート文字列を関数の戻り値として記述しています。この関数を実行することは、このテンプレート文字列を実行することと同じです。

例: テンプレートのコンパイル

次に、テンプレート文字列を使用して正式なテンプレートを生成する例を見てみましょう。

let template = `
<ul>
  <% for(let i=0; i < data.supplies.length; i++) { %>
    <li><%= data.supplies[i] %></li>
  <% } %>
</ul>
`;

上記のコードは、テンプレート文字列内に通常のテンプレートを配置します。このテンプレートは、<%...%> を使用して JavaScript コードを配置し、<%= ... %> を使用して JavaScript 式を出力します。

このテンプレート文字列をコンパイルするにはどうすればよいでしょうか?

1 つのアイデアは、これを JavaScript 式文字列に変換することです。

echo('<ul>');
for(let i=0; i < data.supplies.length; i++) {
  echo('<li>');
  echo(data.supplies[i]);
  echo('</li>');
};
echo('</ul>');

この変換には正規表現を使用するだけです。

let evalExpr = /<%=(.+?)%>/g;
let expr = /<%([\s\S]+?)%>/g;

テンプレート = テンプレート
  .replace(evalExpr, '`); \n echo( $1 );
  .replace(expr, '`); \n $1 \n echo(`');

テンプレート = 'echo(`' + テンプレート + '');';

次に、「テンプレート」を関数にカプセル化して返します。

let スクリプト =
`(関数解析(データ){
  出力 = "" にします。

  関数エコー(html){
    出力 += html;
  }

  ${ テンプレート }

  出力を返します。
})`;

スクリプトを返します。

上記の内容をテンプレートのコンパイル関数 compile にアセンブルします。

関数コンパイル(テンプレート){
  const evalExpr = /<%=(.+?)%>/g;
  const expr = /<%([\s\S]+?)%>/g;

  テンプレート = テンプレート
    .replace(evalExpr, '`); \n echo( $1 );
    .replace(expr, '`); \n $1 \n echo(`');

  テンプレート = 'echo(`' + テンプレート + '');';

  let スクリプト =
  `(関数解析(データ){
    出力 = "" にします。

    関数エコー(html){
      出力 += html;
    }

    ${ テンプレート }

    出力を返します。
  })`;

  スクリプトを返します。
}

compile関数の使い方は以下の通りです。

let parse = eval(compile(template));
div.innerHTML = parse({ 供給: [ "ほうき", "モップ", "クリーナー" ] });
// <ul>
// <li>ほうき</li>
// <li>モップ</li>
// <li>クリーナー</li>
// </ul>

タグテンプレート

テンプレート文字列の機能は上記だけではありません。このテンプレート文字列を処理するために呼び出される関数の名前の後に続けることができます。これは「タグ付きテンプレート」機能と呼ばれます。

警告「こんにちは」
// と同等
アラート(['こんにちは'])

タグ テンプレートは実際にはテンプレートではなく、特別な形式の関数呼び出しです。 「ラベル」は関数を指し、その直後のテンプレート文字列がそのパラメータです。

ただし、テンプレート文字に変数がある場合、これは単純な呼び出しではなく、関数を呼び出す前にテンプレート文字列が複数のパラメーターに処理されます。

a = 5 とします。
b = 10 とします。

tag`Hello ${ a + b } world ${ a * b }`;
// と同等
tag(['Hello ', 'world ', ''], 15, 50);

上記のコードでは、関数であるテンプレート文字列の前に識別名「tag」があります。式全体の戻り値は、テンプレート文字列を処理した後の tag 関数の戻り値になります。

関数 tag は複数のパラメータを順番に受け取ります。

関数タグ(stringArr, value1, value2){
  // ...
}

// と同等

関数タグ(stringArr, ...values){
  // ...
}

tag 関数の最初のパラメータは配列です。配列のメンバーは変数置換を持たないテンプレート文字列の部分です。つまり、変数置換は最初のメンバーと 2 番目のメンバーの間でのみ発生します。 2 番目のメンバーと 3 番目のメンバーの間の配列など。

tag 関数の他のパラメータは、テンプレート文字列内の各変数の置換された値です。この例ではテンプレート文字列に 2 つの変数が含まれているため、tag は 2 つのパラメータ、value1value2 を受け取ります。

tag関数の全パラメータの実際の値は以下の通りです。

  • 最初のパラメータ: ['Hello ', 'world ', '']
  • 2 番目のパラメータ: 15
  • 3番目のパラメータ: 50

つまり、tag 関数は実際には次のような形式で呼び出されます。

tag(['Hello ', 'world ', ''], 15, 50)

必要に応じて「tag」関数をコーディングできます。以下にtag関数の書き方と実行結果を示します。

a = 5 とします。
b = 10 とします。

関数タグ(s、v1、v2) {
  console.log(s[0]);
  console.log(s[1]);
  console.log(s[2]);
  コンソール.ログ(v1);
  コンソール.ログ(v2);

  「OK」を返します。
}

tag`Hello ${ a + b } world ${ a * b}`;
// "こんにちは"
// " 世界 "
//```
// 15
// 50
// "わかりました"

より複雑な例を次に示します。

合計 = 30 とします。
let msg = passthru`合計は ${total} (税込 ${total*1.05})`;

関数パススルー(リテラル) {
  結果 = '';
  i = 0 とします。

  while (i < literals.length) {
    結果 += リテラル[i++];
    if (i < 引数.長さ) {
      結果 += 引数[i];
    }
  }

  結果を返します。
}

msg // "合計は 30 (税込み 31.5) です。"

上の例は、さまざまなパラメータを元の位置に戻す方法を示しています。

passthru 関数は、rest パラメータを受け取り、次のように記述されます。

関数 passthru(リテラル, ...値) {
  出力 = "" にします。
  インデックスを付けます。
  for (インデックス = 0; インデックス < 値.長さ; インデックス++) {
    出力 += リテラル[インデックス] + 値[インデックス];
  }

  出力 += リテラル[インデックス]
  出力を返します。
}

「タグ テンプレート」の重要な用途は、HTML 文字列をフィルタリングして、ユーザーが悪意のあるコンテンツを入力するのを防ぐことです。

メッセージを送信 =
  SaferHTML`<p>${sender} がメッセージを送信しました。</p>`;

function SaferHTML(templateData) {
  let s = templateData[0];
  for (let i = 1; i < argument.length; i++) {
    arg = String(arguments[i]); とします。

    // 置換内の特殊文字をエスケープします。
    s += arg.replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, ">");

    // テンプレート内の特殊文字をエスケープしないでください。
    s += テンプレートデータ[i];
  }
  を返します。
}

上記のコードでは、sender 変数は多くの場合、ユーザーによって提供され、SaferHTML 関数によって処理された後、その中の特殊文字はエスケープされます。

let sender = '<script>alert("abc")</script>' // 悪意のあるコード
let message = SaferHTML`<p>${sender} がメッセージを送信しました。</p>`;

メッセージ
// <p>&lt;script&gt;alert("abc")&lt;/script&gt; からメッセージが送信されました。</p>

ラベル テンプレートのもう 1 つの用途は、多言語変換 (国際化処理) です。

i18n`${siteName} へようこそ。あなたは訪問者番号 ${visitorNumber} です!`
// 「xxx へようこそ、あなたは xxxx の訪問者です!」

テンプレート文字列そのものには条件判定やループ処理機能がないため、Mustacheなどのテンプレートライブラリの代替にはなりませんが、ラベル関数を利用することで、これらの機能を自分で追加することができます。

// 以下の hashTemplate 関数
// カスタムテンプレート処理関数です
let libraryHtml = hashTemplate`
  <ul>
    #${myBooks} の本の場合
      <li><i>#{book.title}</i> by #{book.author}</li>
    #終わり
  </ul>
`;

さらに、タグ テンプレートを使用して、JavaScript 言語に他の言語を埋め込むこともできます。

jsx`
  <div>
    <入力
      ref='入力'
      onChange='${this.handleChange}'
      デフォルト値='${this.state.value}' />
      ${this.state.value}
   </div>
`

上記のコードは、jsx 関数を使用して DOM 文字列を React オブジェクトに変換します。 jsx 関数の [実装] は GitHub (https://gist.github.com/lygaret/a68220defa69174bdec5) で見つけることができます。

以下は、「java」関数を使用して JavaScript コードで Java コードを実行する仮定の例です。

ジャワ
クラス HelloWorldApp {
  public static void main(String[] args) {
    System.out.println("Hello World!"); // 文字列を表示します。
  }
}
`
HelloWorldApp.main();

テンプレート処理関数の最初のパラメータ (テンプレート文字列の配列) にも「raw」属性があります。

コンソール.ログ`123`
// ["123"、生: 配列[1]]

上記のコードでは、「console.log」によって受け入れられるパラメータは実際には配列です。この配列には、エスケープされた元の文字列を格納する「raw」属性があります。

以下の例を参照してください。

タグ`1 行目\n2 行目`

関数タグ(文字列) {
  console.log(strings.raw[0]);
  // strings.raw[0] は「1 行目\\n2 行目」です
  //出力を印刷 "1 行目\n2 行目"
}

上記のコードでは、「tag」関数の最初のパラメータ「strings」には「raw」属性があり、これも配列を指します。この配列のメンバーは、「strings」配列とまったく同じです。たとえば、strings 配列が ["First line\n2nd line"] である場合、strings.raw 配列は ["First line\\n2nd line"] になります。 2 つの唯一の違いは、文字列内のスラッシュがエスケープされることです。たとえば、strings.raw 配列は、「\n」を改行文字ではなく、「\」と「n」の 2 つの文字として扱います。これは、エスケープする前に元のテンプレートを取得しやすくするように設計されています。

テンプレート文字列の制限

前述したように、ラベル テンプレートには他の言語を埋め込むことができます。ただし、テンプレート文字列はデフォルトで文字列をエスケープするため、他の言語を埋め込むことはできません。

たとえば、LaTEX 言語をラベル テンプレートに埋め込むことができます。

関数ラテックス(文字列) {
  // ...
}

let document = ラテックス`
\newcommand{\fun}{\textbf{Fun!}} // 正常に動作します
\newcommand{\unicode}{\textbf{Unicode!}} // エラーレポート
\newcommand{\xerxes}{\textbf{King!}} // エラー報告

\u{h}ere を超えてください // エラー
`

上記のコードでは、変数 document に埋め込まれたテンプレート文字列は LaTEX 言語では完全に正当ですが、JavaScript エンジンはエラーを報告します。理由は文字列のエスケープにあります。

テンプレート文字列は \u00FF\u{42} を Unicode 文字としてエスケープするため、\unicode を解析するとエラーが報告され、\x56 は 16 進文字列として変換されます。 \xerxes はエラーを報告します。言い換えれば、LaTEX では \u\x は特別な意味を持ちますが、JavaScript ではそれらをエスケープします。

この問題を解決するために、ES2018 ではタグ テンプレート内の文字列エスケープに関する制限が 緩和 されます。不正な文字列エスケープが見つかった場合は、エラーを報告する代わりに「未定義」が返され、元の文字列は「生」属性から取得できます。

関数タグ(文字列) {
  strs[0] === 未定義
  strs.raw[0] === "\\unicode と \\u{55}";
}
タグ「\unicode」と「\u{55}」

上記のコードでは、テンプレート文字列は本来エラーを報告するはずですが、文字列のエスケープに関する制限が緩和されているため、JavaScript エンジンは最初の文字を unknown に設定しますが、raw 属性は引き続き機能します。元の文字列なので、tag 関数は引き続き元の文字列を処理できます。

この文字列エスケープの緩和は、ラベル テンプレートが文字列を解析する場合にのみ有効になることに注意してください。ラベル テンプレートでない場合でも、エラーが報告されます。

let bad = `不正なエスケープ シーケンス: \unicode`; // エラー レポート

作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0