テンプレートリテラルとは、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, "&").replace(/</g, "<");
}
return str;
};
var userinput = '<script>alert("XSSテスト");</script>';
var message = tagHtmlEscape`<p>${userinput}</p>`;
console.log(message); // "<p><script>alert("XSSテスト");</script></p>"
マニアック
String.raw`~`
の弱点
エスケープせずそのままの文字を表現できるという魅力がありますが、全ての文字を表現できるわけではありません。
例えば、文字列の最後が \
で終わるようにはできません。
var b = String.raw`ジャバ\スク\\リプト\`;
これは、`ジャバ\スク\\リプト\`
をテンプレートリテラルと見なした際、終わりの \`
がエスケープされているとみなされ、テンプレートリテラルが終わらないからです。
以下のように書けば解決しますが、あまりスマートではありませんね…。
var b = String.raw`ジャバ\スク\\リプト` + "\\";
ES6 のテンプレートリテラル、改善の余地があると感じます。
タグ付きテンプレートリテラルで渡される第一引数の性質
タグ付きテンプレートリテラル(func`~`
)で渡される第一引数には、以下の変わった性質があります。
- プロパティの追加・変更・削除が不可能
- 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文字短い