JavaScript の テンプレートリテラル を極める!

  • 183
    Like
  • 0
    Comment

テンプレートリテラルとは、ECMAScript 6 で新しく使えるようになった構文のひとつです。
言わばヒアドキュメントのようなものです。めちゃくちゃ便利です!
2015年8月現在、Google Chrome と Firefox の最新版では既に使えるようになっています。
2016年9月現在、Edgeでも使えるようになっており、Google Chrome や Firefox も含めたほとんどのモダンブラウザで利用可能です。
(残念ながらモバイルブラウザではまだ非対応のこともあるので、要注意です。)

基本

この内容を知っているだけでもカナリ使えます!

バッククオート(`~`)で囲む!

var a = "ジャバスクリプト";
var b = `ジャバスクリプト`;
console.log( a === b ); // true
console.log( b ); // ジャバスクリプト

つまり、シングルクオート('text') や ダブルクオート("text") と基本は同じです!

改行をそのまま書ける!

var a = "ジャバ\nスクリプト";
var b = `ジャバ
スクリプト`;
console.log( a === b ); // true
console.log( b ); // ジャバ⏎スクリプト

"\n" の代わりに、`⏎` と書くことができるということです。

もちろん、今までのエスケープも併せて使える!

var a = "ジャバ\nスク\nリプト";
var b = `ジャバ\nスク
リプト`;
console.log( a === b ); // true
console.log( b ); // ジャバ⏎スク⏎リプト
var a = "ジャバ\\スク\nリプト";
var b = `ジャバ\\スク
リプト`;
console.log( a === b ); // true
console.log( b ); // ジャバ\スク⏎リプト

`\n` と書くことも、`⏎` と書くこともできるということです。

エスケープせず、そのまま文字にしたいならString.raw

`~`の中では\nみたいなエスケープが可能ですが、場合によっては余計なお世話に感じることもあります。
エスケープせず本当にそのままの文字が欲しい場合は、String.raw`~` を使います。

var a = "ジャバ\\スク\\nリプト";
var b = String.raw`ジャバ\スク\nリプト`;
console.log( a === b ); // true
console.log( b ); // ジャバ\スク\nリプト

つまり、"\\n" のようにエスケープする必要があるところを、String.raw`\n` のようにそのまま書けるということです!

`${~}` の中には、変数や計算式を入れることができる!

var foo = "--";
var a = "ジャバ" + foo + "スク" + (111+222) + "リプト";
var b = `ジャバ${foo}スク${111+222}リプト`;
console.log( a === b ); // true
console.log( b ); // ジャバ--スク333リプト

つまり、+演算子 を使わずに文字列を繋げていくことができます。
`${~}` の中には、式であれば何でも入れることができます!

でも ${} を文字として入れたいときもある。

そんな時は、`\${}``$\{}` のようにエスケープします。

var a = "ジャバ${foo}スクリプト";
var b = `ジャバ\${foo}スクリプト`;
console.log( a === b ); // true
console.log( b ); // ジャバ${foo}スクリプト

もちろん、String.raw`~``${~}` を両方使うことも可能!

var foo = "--";
var a = "ジャバ" + foo + "スク\\リプト";
var b = String.raw`ジャバ${foo}スク\リプト`;
console.log( a === b ); // true
console.log( b ); // ジャバ--スク\リプト

発展

この内容を知っていればもっと便利!
基本はバッチリという人はぜひこっちもマスターしてください!

String.raw`~` の正体

String.raw はただの関数です

func() は関数を実行する構文なのと同様に、
func`~` もまた、関数を実行する構文なのです。
(この構文は、タグ付きテンプレートリテラル(Tagged Templates)と言います。)

ただし、func`~` のように関数を実行した場合、渡される引数がちょっと変わっています
どんな引数が渡されるのか、以下を例にとってみてみましょう。

function tag(){
  console.log(arguments);
}
tag`\\ジャバ${true}スク${1+2}リプト`;
/*
  0: ["\ジャバ", "スク", "リプト", raw: ["\\ジャバ", "スク", "リプト"]]
  1: true (Boolean型)
  2: 3 (Number型)
*/

第1引数には文字列の配列が入っています。リテラル中に ${~} が含まれる場合、${~} の前後で分割されます
さらにその配列の rawプロパティ には、エスケープされていない文字列の配列が入っています。
第2引数以降には ${~} の中身で評価された式の値が順に入っています。(String型に変換されていない点に注意です。)

つまり、String.raw を自前で定義すると以下のようになります。

String.raw = function(strings){
    var str = "";
    var raws = strings.raw;
    var subs = Array.prototype.slice.call(arguments, 1);
    for(var i=0;i<raws.length;i++){
        str += raws[i];
        if(i < raws.length - 1) str += subs[i];
    }
    return str;
}; 
String.raw = ({raw:raws}, ...subs) => {
    let str = "";
    for(let s of raws) str += s + (subs.length ? subs.shift() : "");
    return str;
}; 

自分で定義した関数でタグ付きテンプレートリテラルを使ってみる

HTML のエスケープとかに使えます。

function tagHtmlEscape(strings){
    var str = "";
    var subs = Array.prototype.slice.call(arguments, 1);
    for(var i=0;i<strings.length;i++){
        str += strings[i];
        if(i < strings.length - 1) str += subs[i].replace(/&/g, "&amp;").replace(/</g, "&lt;");
    }
    return str;
}; 
var userinput = '<script>alert("XSSテスト");</script>';
var message = tagHtmlEscape`<p>${userinput}</p>`;
console.log(message); // "<p>&lt;script>alert("XSSテスト");&lt;/script></p>"

マニアック

String.raw`~` の弱点

エスケープせずそのままの文字を表現できるという魅力がありますが、全ての文字を表現できるわけではありません。
例えば、文字列の最後が \ で終わるようにはできません。

エラー!
var b = String.raw`ジャバ\スク\\リプト\`;

これは、`ジャバ\スク\\リプト\` をテンプレートリテラルと見なした際、終わりの \` がエスケープされているとみなされ、テンプレートリテラルが終わらないからです。

以下のように書けば解決しますが、あまりスマートではありませんね…。

var b = String.raw`ジャバ\スク\\リプト` + "\\";

ES6 のテンプレートリテラル、改善の余地があると感じます。

タグ付きテンプレートリテラルで渡される第一引数の性質

タグ付きテンプレートリテラル(func`~`)で渡される第一引数には、以下の変わった性質があります。
1. プロパティの追加・変更・削除が不可能
2. rawの文字列の配列の要素がすべて一致する場合、何度実行しても同じ配列オブジェクトを参照する

以上の性質を示すコードが以下になります。

function tag(ary){ return ary; }
var a = tag`ジャバ${123}スクリプト`;
var b = tag`ジャバ${456}スクリプト`;
console.log(a); // ["ジャバ", "スクリプト", raw: ["ジャバ", "スクリプト"]]
a[0] = "JAVA"; // 変更不可
delete a[1]; // 削除不可
a[2] = "!"; // 追加不可
console.log(a); // ["ジャバ", "スクリプト", raw: ["ジャバ", "スクリプト"]]
console.log(a === b); // 同じオブジェクトを指している

改行コードは気にしなくていい

改行コードには、CR、LF、CR+LF の3種類があります。
それでは、`~` の中に改行を入れた時、改行コードを意識しないといけないのかというと、その必要はありません。
`~` の中の改行は自動的に全て LF に統一されます

var a = eval("`ジャバ\rスクリプト`");
var b = eval("`ジャバ\nスクリプト`");
var c = eval("`ジャバ\r\nスクリプト`");
var s = "ジャバ\nスクリプト";
console.log(a === s, b === s, c === s); // true, true, true

もっと細かな話をすると、実は HTML 内に書いた改行コードは全てパースする時点で LF に統一されるのですが…。これはまた別の機会に。

ショートコーディング

タグ付きテンプレートリテラルの性質を使えば、面白いショートコーディングができます。

console.log( "abcde".split`` ); // .split("") より2文字短い
console.log( [1,2,3].join`` ); // .join("") より2文字短い
console.log( "aabbcc".split`b`.join`B` ); // .replace(/b/g,"B") より 1文字短い
console.log( "abcde".match`b.d` ); // .match(/b.d/) より2文字短い
console.log( [1,2,3].push`` ); // .length+1 より 2文字短い