- JavaScript
JavaScriptテンプレートリテラルの使い方|変数埋め込み・複数行
テンプレートリテラルとは、文字列をバッククォート(`)で囲み、${ } の中に変数や式を書いて埋め込める JavaScript の文字列の書き方です。 "こんにちは、" + name + "さん" と書いていたものが `こんにちは、${name}さん` と1つの文字列で書け、改行もそのまま入れられます。ES2015(ES6)で導入され、現在のブラウザーと Node.js のすべてで使えます。
この記事では、+ 連結からの書き換え対応表と、${} に書ける式のコピペ集を、すべて実行結果つきで載せました。 さらに、複数行で書いたときにインデントの空白が混ざる再現例と対処、innerHTML に埋め込んだときにスクリプトが動く再現例とエスケープ関数、String.raw、タグ付きテンプレートまで扱います。実行結果は Node.js v22.22.2 と Chromium 141 で実際に動かして確かめたものです。
テンプレートリテラルの早見表
やりたいことから書き方を探せる表です。
| やりたいこと | 書き方 | 結果 |
|---|---|---|
| 変数を埋め込む | `こんにちは、${name}さん` |
こんにちは、佐藤さん |
| 計算結果を埋め込む | `合計: ${price * qty}円` |
合計: 3600円 |
| 条件で出し分ける | `${score >= 60 ? "合格" : "不合格"}` |
合格 |
| 配列を並べる | `${list.join("・")}` |
赤・青・緑 |
| 配列からHTMLを作る | `${items.map(i => `<li>${i.name}</li>`).join("")}` |
<li>ペン</li><li>ノート</li> |
| 値がないときの代わり | `${user.phone ?? "未登録"}` |
未登録 |
| 複数行の文字列 | バッククォートの中でそのまま改行 | 改行(\n)を含む文字列 |
${ を文字として出す |
`\${name}` |
${name} |
| バッククォートを文字として出す | `\`code\`` |
`code` |
\ をそのまま残す |
String.raw`C:\new\table` |
C:\new\table |
| 埋め込む値を加工する | safeHtml`<p>${input}</p>`(タグ付きテンプレート) |
エスケープ済みのHTML |
(name = "佐藤"、price = 1200、qty = 3、score = 72、list = ["赤", "青", "緑"] のとき)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JavaScriptテンプレートリテラルの使い方|変数埋め込み・複数行」
基本の書き方:バッククォートと ${}
3つのルール
テンプレートリテラルのルールは3つだけです。
- 文字列をバッククォート(
`)で囲む。日本語キーボードでは Shift + @ で入力できます。 - 埋め込みたい変数や式を
${ }の中に書く。 - 改行はそのまま書ける。
const name = "佐藤";
const age = 28;
console.log(`こんにちは、${name}さん`); // こんにちは、佐藤さん
console.log(`${name}(${age}歳)`); // 佐藤(28歳)
console.log(typeof `x`); // string
console.log(`a` === "a"); // true
テンプレートリテラルで作った値は、ふつうの文字列(string)です。"a" と `a` は同じ値で、length や toUpperCase() などのメソッドもそのまま使えます。
クォートの種類を間違えると埋め込まれない
${} が働くのはバッククォートの中だけです。 シングルクォートやダブルクォートの中では、ただの文字として出力されます。
const name = "佐藤";
console.log('${name}', "${name}", `${name}`);
// ${name} ${name} 佐藤
画面に ${name} がそのまま表示されたときは、囲んでいる記号がバッククォートになっているかを確かめてみてください。
| 囲む記号 | ${} の埋め込み |
そのまま改行 | 中に書ける引用符 |
|---|---|---|---|
'...'(シングル) |
できない | できない | " はそのまま、' は \' |
"..."(ダブル) |
できない | できない | ' はそのまま、" は \" |
`...`(バッククォート) |
できる | できる | ' も " もそのまま、` は \` |
+連結からの書き換え対応表
これまで + でつないでいた書き方を、テンプレートリテラルに置き換える対応表です。 どの行も、左右で同じ文字列になることを Node.js v22.22.2 で確かめました。
| やりたいこと | + 連結 |
テンプレートリテラル | 結果 |
|---|---|---|---|
| 変数を1つ埋め込む | "こんにちは、" + name + "さん" |
`こんにちは、${name}さん` |
こんにちは、佐藤さん |
| 変数を2つ埋め込む | name + "(" + age + "歳)" |
`${name}(${age}歳)` |
佐藤(28歳) |
| 掛け算の結果 | "合計: " + price * qty + "円" |
`合計: ${price * qty}円` |
合計: 3600円 |
| 足し算の結果 | "合計: " + (price + 100) + "円" |
`合計: ${price + 100}円` |
合計: 1300円 |
| 改行を入れる | "1行目\n" + "2行目" |
バッククォート内でそのまま改行 | 1行目(改行)2行目 |
| 引用符を含む | "It's \"OK\"" |
`It's "OK"` |
It's "OK" |
| URLを組み立てる | "/users/" + id + "/posts?page=" + page |
`/users/${id}/posts?page=${page}` |
/users/42/posts?page=2 |
| HTMLを組み立てる | "<li class=\"item\">" + name + "</li>" |
`<li class="item">${name}</li>` |
<li class="item">佐藤</li> |
書き換えると計算の間違いにも気づきやすい
+ は「文字列の連結」と「数値の足し算」の両方に使われるため、かっこを忘れると意図しない結果になります。
const price = 1200;
console.log("合計: " + price + 100 + "円"); // 合計: 1200100円 (文字列として連結された)
console.log("合計: " + (price + 100) + "円"); // 合計: 1300円
console.log(`合計: ${price + 100}円`); // 合計: 1300円
const a = 1, b = 2;
console.log("a+b=" + a + b); // a+b=12
console.log(`a+b=${a + b}`); // a+b=3
テンプレートリテラルでは ${ } の中が先に計算され、その結果が文字列に変換されて埋め込まれます。「どこまでが計算で、どこからが文字か」が見た目ではっきりするので、かっこの付け忘れが起きにくくなります。
${} に書ける式のコピペ集
${ } の中には、値を返す「式」なら何でも書けます。 変数、計算、関数呼び出し、三項演算子、メソッドのつなぎ書きなどです。次のデータを使った実行結果を載せます。
const name = "佐藤", price = 1200, qty = 3, n = 5, score = 72;
const list = ["赤", "青", "緑"];
const user = { name: "田中", address: { city: "東京" }, tags: ["a", "b"] };
const items = [{ name: "ペン", price: 120 }, { name: "ノート", price: 300 }];
const d = new Date(2026, 9, 3);
計算・数値の整形
`${1 + 2}` // "3"
`${price * qty}円` // "3600円"
`${(price * 1.1).toFixed(0)}円` // "1320円"
`${price.toLocaleString()}円` // "1,200円"
`${(0.256 * 100).toFixed(1)}%` // "25.6%"
`${String(7).padStart(3, "0")}` // "007"
`${new Intl.NumberFormat("ja-JP").format(1234567)}` // "1,234,567"
条件で出し分ける(三項演算子)
`${score >= 60 ? "合格" : "不合格"}` // "合格"
`${n}件${n > 1 ? "以上" : ""}` // "5件以上"
`${n > 9 ? "多い" : ""}` // ""
`${n > 3 && "多い"}` // "多い"
`${n > 9 && "多い"}` // "false" (false が文字として出る)
${ } の中に if 文は書けないので、条件分岐には三項演算子(条件 ? A : B)を使います。 && で書くと、条件が成り立たないときに false という文字がそのまま出力されます。「何も出さない」場合は 条件 ? "..." : "" と書くと意図どおりになります。
オブジェクトのプロパティと、値がないときの代わり
`${user.address.city}` // "東京"
`${user.phone ?? "未登録"}` // "未登録"
`${user.company?.name ?? "なし"}` // "なし"
`${JSON.stringify(user.tags)}` // "[\"a\",\"b\"]" → 表示は ["a","b"]
`${user}` // "[object Object]"
オブジェクトをそのまま埋め込むと [object Object] になります。中身を見たいときは JSON.stringify(obj) を使います。存在しないプロパティは undefined という文字になるので、??(null合体演算子)で代わりの文字を指定しておくと表示が整います。
配列:join と map().join()
`${list.length}色` // "3色"
`${list.join("・")}` // "赤・青・緑"
`${list}` // "赤,青,緑"
`${list.map(c => `[${c}]`).join("")}` // "[赤][青][緑]"
`${items.map(i => `${i.name}:${i.price}円`).join(", ")}` // "ペン:120円, ノート:300円"
`${items.reduce((s, i) => s + i.price, 0)}円` // "420円"
`${[1, 2, 3].includes(2) ? "あり" : "なし"}` // "あり"
配列をそのまま埋め込むとカンマ区切りになります。 map() で作った配列も同じで、.join("") を付け忘れると <li>…</li>,<li>…</li> のようにカンマが入ります。区切り文字を決めたいときは必ず join() を付けます。
関数呼び出し・メソッド・日付
`${"sato".toUpperCase()}` // "SATO"
`${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}` // "2026/10/3"
`${(() => { if (n > 3) return "大"; return "小"; })()}` // "大"
`${typeof `${1}`}` // "string"
自作の関数も同じように呼べます。引数に初期値を付けた、あいさつ文を作る関数の例です。
const greet = (name = "ゲスト", hour = 9) =>
`${hour < 12 ? "おはようございます" : "こんにちは"}、${name}さん`;
console.log(greet()); // おはようございます、ゲストさん
console.log(greet("田中", 15)); // こんにちは、田中さん
ネスト:テンプレートリテラルの中にテンプレートリテラル
${ } の中に、さらにバッククォートの文字列を書けます。 内側のバッククォートをエスケープする必要はありません。
const isAdmin = true, cnt = 3;
console.log(`${isAdmin ? `管理者(${cnt}件の通知)` : "一般"}`);
// 管理者(3件の通知)
const html = `<ul>
${items.map(i => ` <li>${i.name} - ${i.price}円</li>`).join("\n")}
</ul>`;
console.log(html);
<ul>
<li>ペン - 120円</li>
<li>ノート - 300円</li>
</ul>
ネストが2段を超えると読みにくくなるので、内側を変数や関数に切り出すと見通しがよくなります。
const row = (i) => ` <li>${i.name} - ${i.price}円</li>`;
const html2 = `<ul>\n${items.map(row).join("\n")}\n</ul>`;
値が文字列に変わるときのルール
${ } に入れた値は、文字列に変換されてから埋め込まれます。型ごとの結果を表にまとめます。
| 埋め込む値 | 結果 | メモ |
|---|---|---|
null |
"null" |
そのまま文字になる |
undefined |
"undefined" |
?? で代わりの値を指定 |
true / false |
"true" / "false" |
&& の結果に注意 |
NaN |
"NaN" |
計算に数値以外が混ざったサイン |
[](空の配列) |
"" |
空文字 |
[1, [2, 3]] |
"1,2,3" |
入れ子もカンマ区切り |
{}(オブジェクト) |
"[object Object]" |
JSON.stringify を使う |
0.1 + 0.2 |
"0.30000000000000004" |
toFixed で桁をそろえる |
10n ** 20n(BigInt) |
"100000000000000000000" |
末尾の n は付かない |
Symbol("x") |
TypeError: Cannot convert a Symbol value to a string |
String(sym) なら変換できる |
| 定義していない変数 | ReferenceError: notDefined is not defined |
変数名のつづりを確かめる |
自作クラスに toString() を定義すると、埋め込んだときの表示を決められます。
class Money {
constructor(v) { this.v = v; }
toString() { return `${this.v.toLocaleString("ja-JP")}円`; }
}
console.log(`価格は${new Money(12800)}です`); // 価格は12,800円です
toString() と valueOf() の両方を持つオブジェクトでは、テンプレートリテラルは toString() を使い、obj + "" は valueOf() を使いました(結果はそれぞれ "TS" と "42")。+ 連結から書き換えるときに表示が変わることがある、数少ない場面です。
複数行の文字列とインデントの扱い
そのまま改行できる
バッククォートの中の改行は、そのまま改行文字(\n)として文字列に入ります。
const text = `a
b`;
console.log(JSON.stringify(text)); // "a\nb"
console.log(text.length); // 3
console.log(text === "a\nb"); // true
再現:関数の中で書くとインデントの空白が混ざる
ソースコードの字下げ(インデント)も文字列の一部になります。 関数の中で見た目をそろえて書くと、行頭の空白や前後の改行がそのまま入ります。
function makeMail(name) {
const text = `
${name} 様
ご登録ありがとうございます。
`;
return text;
}
console.log(JSON.stringify(makeMail("佐藤")));
"\n 佐藤 様\n ご登録ありがとうございます。\n "
先頭に改行が1つ、各行の頭に空白が8個、末尾に改行と空白4個が入っています。HTMLとして表示する場合は連続した空白がまとめられるので目立ちませんが、メール本文、ログ、テキストファイル、<pre> や <textarea> の中身では、この空白がそのまま見えます。
対処4通り
| 方法 | 書き方 | 向いている場面 |
|---|---|---|
配列を join("\n") でつなぐ |
[行1, 行2].join("\n") |
行数が少ない。インデントを保ったまま書ける |
各行を trim() する |
.split("\n").map(l => l.trim()).filter(Boolean).join("\n") |
行頭の空白をすべて取り除いてよい文章 |
| 共通のインデントだけ取り除く(dedent) | 下のタグ関数 | 文中の字下げは残したい |
行末の \ で改行を打ち消す |
行末に \ を書く |
長い1行をソース上だけ折り返したい |
// 方法1:配列を join でつなぐ
function makeMail2(name) {
return [
`${name} 様`,
"ご登録ありがとうございます。",
].join("\n");
}
console.log(JSON.stringify(makeMail2("佐藤")));
// "佐藤 様\nご登録ありがとうございます。"
// 方法2:各行を trim する
function makeMail4(name) {
return `
${name} 様
ご登録ありがとうございます。
`.split("\n").map(l => l.trim()).filter(Boolean).join("\n");
}
console.log(JSON.stringify(makeMail4("佐藤")));
// "佐藤 様\nご登録ありがとうございます。"
共通のインデントだけを取り除く dedent 関数は、後述のタグ付きテンプレートで書けます。各行の先頭の空白のうち、いちばん少ない数だけを全行から削ります。
function dedent(strings, ...values) {
const raw = strings.reduce(
(acc, s, i) => acc + s + (i < values.length ? String(values[i]) : ""), "");
const lines = raw.replace(/^\n/, "").replace(/\n[ \t]*$/, "").split("\n");
const indent = Math.min(
...lines.filter(l => l.trim()).map(l => l.match(/^[ \t]*/)[0].length));
return lines.map(l => l.slice(indent)).join("\n");
}
function makeMail3(name) {
return dedent`
${name} 様
ご登録ありがとうございます。
・特典A
`;
}
console.log(JSON.stringify(makeMail3("佐藤")));
// "佐藤 様\nご登録ありがとうございます。\n ・特典A"
3行目の「・特典A」の前にある2個の空白は残り、共通の8個だけが取り除かれています。
行末に \(バックスラッシュ)を書くと、その改行は文字列に入りません。 長い文をソースコード上だけで折り返したいときに使えます。
const long = `この文は長いので\
途中で折り返しています`;
console.log(JSON.stringify(long)); // "この文は長いので途中で折り返しています"
前後の空白と改行だけを取り除きたい場合は trim() が手軽です。`\na\nb\n`.trim() は "a\nb" になります。
innerHTMLに埋め込むときのXSSとエスケープ関数
再現:入力値をそのまま埋め込むとスクリプトが動く
テンプレートリテラルはHTMLを組み立てるのに便利ですが、利用者が入力した値をそのまま innerHTML に入れると、値の中のHTMLタグがタグとして解釈されます。 この性質を使ってスクリプトを動かされる問題を、XSS(クロスサイトスクリプティング)と呼びます。
Chromium 141(Playwright 経由)で次のページを開いて確かめました。window.fired は、スクリプトが動いたかどうかを記録するための配列です。
<!doctype html><meta charset="utf-8">
<div id="a"></div><div id="b"></div><div id="c"></div>
<script>
window.fired = [];
const escapeHtml = (v) => String(v)
.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
.replaceAll('"', """).replaceAll("'", "'");
// コメント欄に入力された値、という想定
const comment = `<img src=x onerror="window.fired.push('innerHTML')">こんにちは`;
// (a) そのまま埋め込む
document.getElementById("a").innerHTML = `<p>${comment}</p>`;
// (b) エスケープしてから埋め込む
document.getElementById("b").innerHTML = `<p>${escapeHtml(comment)}</p>`;
// (c) textContent で入れる
const p = document.createElement("p");
p.textContent = comment;
document.getElementById("c").append(p);
</script>
| 方法 | img 要素の数 | onerror が動いたか | 画面に表示される文字 |
|---|---|---|---|
(a) そのまま innerHTML |
1 | 動いた(window.fired は ["innerHTML"]) |
こんにちは |
(b) escapeHtml してから innerHTML |
0 | 動かない | <img src=x onerror=...>こんにちは(文字として表示) |
(c) textContent |
0 | 動かない | (b) と同じ |
(a) では、入力値の <img> が本物の要素になり、画像の読み込みエラーをきっかけに onerror のコードが実行されました。(b) と (c) では < が < に置き換わり、ただの文字として表示されています。
なお、innerHTML で入れた <script> タグは実行されませんでした(同じ環境で確認)。<script> が動かないからといって安心はできず、onerror などのイベント属性でコードが動くという点がポイントです。
エスケープ関数(コピペ用)
HTMLで特別な意味を持つ5文字を、文字として表示するための書き方に置き換えます。
const escapeHtml = (v) => String(v)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
console.log(escapeHtml(`Tom & "Jerry" <b>'s</b>`));
// Tom & "Jerry" <b>'s</b>
& を最初に置き換えるのがポイントです(あとから置き換えると、< の & まで二重に変換されます)。" と ' の置き換えは、属性値に埋め込むときに効きます。
const v = `" onmouseover="alert(1)`;
// そのまま:<input value="" onmouseover="alert(1)"> → 属性は value と onmouseover の2つ
// escapeHtml(v) を使う → 属性は value の1つだけ。値は「" onmouseover="alert(1)」という文字
Chromium で属性名を調べると、そのまま埋め込んだ場合は ["value", "onmouseover"]、エスケープした場合は ["value"] でした。
どの方法を選ぶか
| 状況 | 選ぶ方法 |
|---|---|
| 文字だけを表示する | textContent を使う(エスケープ不要でいちばん確実) |
| HTMLの骨組みに値を差し込む | 値を escapeHtml() に通してから innerHTML へ |
| 埋め込みが多く、通し忘れを減らしたい | 後述のタグ付きテンプレート safeHtml |
href や src に値を入れる |
エスケープに加えて、http: / https: で始まるかを確かめる |
| React・Vue などを使っている | フレームワークの通常の埋め込み(自動でエスケープされる)を使う |
href については、escapeHtml("javascript:alert(1)") を通しても文字が変わらず、リンクのプロトコルは javascript: のままでした。エスケープはタグや属性の区切りを守るもので、URLの中身の確認は別に行います。 XSS全体の対策は範囲が広いため、この記事ではテンプレートリテラルで起きやすい場面に絞っています。
URLのパラメータに値を入れるときは、HTMLのエスケープではなく encodeURIComponent を使います。
const q = "テンプレート リテラル&x=1";
console.log(`/search?q=${q}`);
// /search?q=テンプレート リテラル&x=1 (& 以降が別のパラメータとして扱われる)
console.log(`/search?q=${encodeURIComponent(q)}`);
// /search?q=%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%20%E3%83%AA%E3%83%86%E3%83%A9%E3%83%AB%26x%3D1
String.raw:バックスラッシュをそのまま残す
String.raw`...` と書くと、\n や \t が改行やタブに変換されず、書いたとおりの文字として残ります。 Windows のパスや正規表現のように \ を多く含む文字列で便利です。
console.log(JSON.stringify(`C:\new\table`)); // "C:\new\table" ← \n と \t が改行とタブになった
console.log(`C:\new\table`.length); // 10
console.log(String.raw`C:\new\table`); // C:\new\table
console.log(String.raw`C:\new\table`.length); // 12
1行目の JSON 表示の \n と \t は、改行文字とタブ文字が1文字ずつ入っていることを表しています。
正規表現を文字列から作るとき
new RegExp() に文字列を渡すとき、ふつうの文字列では \d の \ が消えます。String.raw なら書いたとおりに渡せます。
console.log(new RegExp("^\d{3}-\d{4}$").test("123-4567")); // false
console.log(new RegExp(String.raw`^\d{3}-\d{4}$`).test("123-4567")); // true
console.log(new RegExp("^\\d{3}-\\d{4}$").test("123-4567")); // true(\ を2つ書く方法)
String.raw で知っておきたいこと
| 書き方 | 結果 | 説明 |
|---|---|---|
String.raw`${1 + 2}\n` |
3\n |
${} の埋め込みは働く。\n は2文字のまま |
String.raw`a\${x}` |
a\${x} |
\$ と書くと埋め込まれず、\ も残る |
String.raw`C:\dir\` |
SyntaxError |
末尾が \ だと閉じのバッククォートが打ち消される |
String.raw({ raw: ["a", "b", "c"] }, 1, 2) |
a1b2c |
関数として呼ぶ形。文字と値を交互に連結する |
`C:\users`(String.raw なし) |
SyntaxError: Invalid Unicode escape sequence |
\u のあとに4桁の16進数が必要なため |
String.raw`C:\users\x` |
C:\users\x |
String.raw ならそのまま書ける |
末尾を \ で終えたいときは、String.raw`C:\dir` + "\\" のように最後の1文字だけ別に足します。
タグ付きテンプレート:埋め込む値を関数で加工する
しくみ
バッククォートの直前に関数名を書くと、その関数が「文字の部分」と「埋め込む値」を別々に受け取ります。 これをタグ付きテンプレートと呼びます。String.raw もこの仕組みで作られた関数の1つです。
const name = "佐藤", age = 28;
function tag(strings, ...values) {
console.log(strings, strings.raw, values);
return "done";
}
console.log(tag`名前:${name} 年齢:${age}歳`);
[ '名前:', ' 年齢:', '歳' ] [ '名前:', ' 年齢:', '歳' ] [ '佐藤', 28 ]
done
| 引数 | 中身 | 例 |
|---|---|---|
第1引数 strings |
${} で区切られた文字の部分の配列。要素数は「値の数+1」 |
['名前:', ' 年齢:', '歳'] |
strings.raw |
エスケープを処理する前の文字の配列 | \n が2文字のまま入る |
第2引数以降 ...values |
${} の中の式を評価した値。文字列に変換される前の元の型 |
['佐藤', 28] |
値が文字列になる前に受け取れるのが特徴です。28 は数値のまま届くので、型に応じた加工ができます。また、関数の戻り値は文字列でなくても構いません。
例1:埋め込んだ値を強調する
function highlight(strings, ...values) {
return strings.reduce(
(acc, s, i) => acc + s + (i < values.length ? `【${values[i]}】` : ""), "");
}
console.log(highlight`名前:${name} 年齢:${age}歳`);
// 名前:【佐藤】 年齢:【28】歳
strings.reduce(...) で「文字 → 値 → 文字 → 値 → 文字」の順に組み立てるのが、タグ関数の基本の形です。
例2:自動でエスケープする safeHtml
埋め込む値だけを escapeHtml に通すタグ関数を作ると、通し忘れを防げます。 開発者が書いたHTMLの骨組み(strings)はそのまま、外から来た値(values)だけがエスケープされます。
const escapeHtml = (v) => String(v)
.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
.replaceAll('"', """).replaceAll("'", "'");
function safeHtml(strings, ...values) {
return strings.reduce(
(acc, s, i) => acc + s + (i < values.length ? escapeHtml(values[i]) : ""), "");
}
const input = `<img src=x onerror="alert(1)">`;
console.log(`<p>${input}</p>`);
// <p><img src=x onerror="alert(1)"></p>
console.log(safeHtml`<p>${input}</p>`);
// <p><img src=x onerror="alert(1)"></p>
例3:SQLのプレースホルダーに分ける
値を文字列に混ぜず、「SQL文」と「値の配列」に分けて返すタグ関数です。データベースのライブラリが提供するプレースホルダーの仕組みに、そのまま渡せる形になります。
function sql(strings, ...values) {
return {
text: strings.reduce((a, s, i) => a + s + (i < values.length ? `$${i + 1}` : ""), ""),
values,
};
}
console.log(sql`SELECT * FROM users WHERE id = ${42} AND name = ${"O'Brien"}`);
{
text: 'SELECT * FROM users WHERE id = $1 AND name = $2',
values: [ 42, "O'Brien" ]
}
' を含む値も、SQL文の中には入らず values 側に分かれています。
タグ付きテンプレートの細かな仕様
MDN のテンプレートリテラルのページで確認し、Node.js v22.22.2 でも同じ結果になった仕様です。
| 仕様 | 確認した結果 |
|---|---|
同じ場所のタグ付きテンプレートは、何度評価しても同じ strings 配列を受け取る |
2回呼んで strings を比べると true。キャッシュのキーに使える |
strings は変更できない配列 |
Object.isFrozen(strings) が true |
| タグ付きでは不正なエスケープがエラーにならない(ES2018以降) | log`\unicode` で strings[0] は undefined、strings.raw[0] は \unicode |
| タグなしでは不正なエスケープは構文エラー | `C:\users` は SyntaxError: Invalid Unicode escape sequence |
+連結とテンプレートリテラルの使い分け
どちらも同じ文字列を作れるので、読みやすいほうを選びます。
| 場面 | 選ぶ書き方 | 理由 |
|---|---|---|
| 変数や式を1つ以上埋め込む | テンプレートリテラル | 区切りが見やすく、かっこの付け忘れが起きにくい |
| 複数行の文字列 | テンプレートリテラル | \n を書かずに済む |
' と " が両方出てくる |
テンプレートリテラル | エスケープが不要 |
| 埋め込みのない短い文字列 | "..." または '...' |
見た目がシンプル。チームのルールに合わせる |
| 文字列を2つつなぐだけ | どちらでも | a + b も `${a}${b}` も同じ結果 |
| 行頭の空白を入れたくない複数行 | 配列の join("\n") か dedent |
インデントの混入を避けられる |
| 何度も使う文面 | 関数にする | const tpl = (o) => `こんにちは、${o.name}さん`; |
テンプレートリテラルは書いた時点で評価されるので、あとから値を差し込みたい文面は関数にしておきます。
const tpl = (o) => `こんにちは、${o.name}さん`;
console.log(tpl({ name: "鈴木" })); // こんにちは、鈴木さん
つまずきやすい点の一覧
| 症状 | 原因 | 直し方 |
|---|---|---|
${name} がそのまま表示される |
' や " で囲んでいる |
バッククォートで囲む |
[object Object] と表示される |
オブジェクトをそのまま埋め込んだ | プロパティを指定するか JSON.stringify |
undefined と表示される |
値がない | ${value ?? "代わりの文字"} |
| リストにカンマが入る | map() のあとの join("") がない |
.join("") を付ける |
false と表示される |
条件 && "..." を使った |
条件 ? "..." : "" |
| 行頭に空白が入る | ソースのインデントが文字列に入った | join("\n")・trim()・dedent |
| 入力値のタグが動く | そのまま innerHTML に入れた |
textContent か escapeHtml |
\n がそのまま出ない/パスが崩れる |
\ がエスケープとして処理された |
String.raw を使う |
よくある質問
Q. テンプレートリテラルはどのブラウザーで使えますか? A. MDN の互換性情報では「Baseline(広く利用可能)」とされ、2015年9月以降の主要ブラウザーで使えます。Node.js でもそのまま使えます(この記事は v22.22.2 で確認)。
Q. ${} の中に if 文や for 文は書けますか?
A. 書けません。${} に書けるのは値を返す「式」です。条件分岐は三項演算子、繰り返しは map().join("") を使います。処理が長くなるときは、関数に切り出して ${関数名(引数)} と呼ぶと読みやすくなります。
Q. + 連結とテンプレートリテラルで、できあがる文字列に違いはありますか?
A. 通常は同じ文字列になります(`x${a}` === "x" + a は true)。違いが出るのは、toString() と valueOf() の両方を定義したオブジェクトを埋め込むときなど、ごく一部です。
Q. バッククォートや ${ を文字として出したいときは?
A. \ を前に付けます。`\`code\`` は `code`、`\${name}` は ${name} という文字になります。
まとめ
- テンプレートリテラルはバッククォートで囲み、
${ }に変数や式を書く。'や"の中では埋め込まれない。 +連結より区切りがはっきりし、"合計: " + price + 100のような計算の取り違えに気づきやすい。${ }には三項演算子・関数呼び出し・map().join("")・ネストが書ける。if文は書けない。- 配列は
join()を付け、条件は? :で書く。&&だとfalseが文字として出る。 - 複数行ではソースのインデントも文字列に入る。
join("\n")・trim()・dedent で整える。 - 入力値を
innerHTMLに埋め込むときはescapeHtmlを通すかtextContentを使う。 タグ付きテンプレートsafeHtmlで通し忘れを減らせる。 String.rawは\をそのまま残す。 パスや正規表現の文字列に向いている。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JavaScriptテンプレートリテラルの使い方|変数埋め込み・複数行」
株式会社RJCでは、JavaScriptのテンプレートリテラルや文字列の扱い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。