JavaScript
ECMAScript

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

テンプレートリテラルとは、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文字短い